001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.http.server.reactive;
018
019import java.io.IOException;
020import java.lang.reflect.Field;
021import java.net.URISyntaxException;
022import java.nio.ByteBuffer;
023import java.nio.charset.Charset;
024
025import javax.servlet.AsyncContext;
026import javax.servlet.ServletInputStream;
027import javax.servlet.ServletRequest;
028import javax.servlet.ServletResponse;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletRequestWrapper;
031import javax.servlet.http.HttpServletResponse;
032import javax.servlet.http.HttpServletResponseWrapper;
033
034import org.apache.catalina.connector.CoyoteInputStream;
035import org.apache.catalina.connector.CoyoteOutputStream;
036import org.apache.catalina.connector.RequestFacade;
037import org.apache.catalina.connector.ResponseFacade;
038import org.apache.coyote.Request;
039import org.apache.coyote.Response;
040
041import org.springframework.core.io.buffer.DataBuffer;
042import org.springframework.core.io.buffer.DataBufferFactory;
043import org.springframework.core.io.buffer.DataBufferUtils;
044import org.springframework.http.HttpHeaders;
045import org.springframework.http.MediaType;
046import org.springframework.util.Assert;
047import org.springframework.util.ReflectionUtils;
048
049/**
050 * {@link ServletHttpHandlerAdapter} extension that uses Tomcat APIs for reading
051 * from the request and writing to the response with {@link ByteBuffer}.
052 *
053 * @author Violeta Georgieva
054 * @author Brian Clozel
055 * @since 5.0
056 * @see org.springframework.web.server.adapter.AbstractReactiveWebInitializer
057 */
058public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
059
060
061        public TomcatHttpHandlerAdapter(HttpHandler httpHandler) {
062                super(httpHandler);
063        }
064
065
066        @Override
067        protected ServletServerHttpRequest createRequest(HttpServletRequest request, AsyncContext asyncContext)
068                        throws IOException, URISyntaxException {
069
070                Assert.notNull(getServletPath(), "Servlet path is not initialized");
071                return new TomcatServerHttpRequest(
072                                request, asyncContext, getServletPath(), getDataBufferFactory(), getBufferSize());
073        }
074
075        @Override
076        protected ServletServerHttpResponse createResponse(HttpServletResponse response,
077                        AsyncContext asyncContext, ServletServerHttpRequest request) throws IOException {
078
079                return new TomcatServerHttpResponse(
080                                response, asyncContext, getDataBufferFactory(), getBufferSize(), request);
081        }
082
083
084        private static final class TomcatServerHttpRequest extends ServletServerHttpRequest {
085
086                private static final Field COYOTE_REQUEST_FIELD;
087
088                private final int bufferSize;
089
090                private final DataBufferFactory factory;
091
092                static {
093                        Field field = ReflectionUtils.findField(RequestFacade.class, "request");
094                        Assert.state(field != null, "Incompatible Tomcat implementation");
095                        ReflectionUtils.makeAccessible(field);
096                        COYOTE_REQUEST_FIELD = field;
097                }
098
099                TomcatServerHttpRequest(HttpServletRequest request, AsyncContext context,
100                                String servletPath, DataBufferFactory factory, int bufferSize)
101                                throws IOException, URISyntaxException {
102
103                        super(createTomcatHttpHeaders(request), request, context, servletPath, factory, bufferSize);
104                        this.factory = factory;
105                        this.bufferSize = bufferSize;
106                }
107
108                private static HttpHeaders createTomcatHttpHeaders(HttpServletRequest request) {
109                        RequestFacade requestFacade = getRequestFacade(request);
110                        org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request)
111                                        ReflectionUtils.getField(COYOTE_REQUEST_FIELD, requestFacade);
112                        Assert.state(connectorRequest != null, "No Tomcat connector request");
113                        Request tomcatRequest = connectorRequest.getCoyoteRequest();
114                        TomcatHeadersAdapter headers = new TomcatHeadersAdapter(tomcatRequest.getMimeHeaders());
115                        return new HttpHeaders(headers);
116                }
117
118                private static RequestFacade getRequestFacade(HttpServletRequest request) {
119                        if (request instanceof RequestFacade) {
120                                return (RequestFacade) request;
121                        }
122                        else if (request instanceof HttpServletRequestWrapper) {
123                                HttpServletRequestWrapper wrapper = (HttpServletRequestWrapper) request;
124                                HttpServletRequest wrappedRequest = (HttpServletRequest) wrapper.getRequest();
125                                return getRequestFacade(wrappedRequest);
126                        }
127                        else {
128                                throw new IllegalArgumentException("Cannot convert [" + request.getClass() +
129                                                "] to org.apache.catalina.connector.RequestFacade");
130                        }
131                }
132
133                @Override
134                protected DataBuffer readFromInputStream() throws IOException {
135                        ServletInputStream inputStream = ((ServletRequest) getNativeRequest()).getInputStream();
136                        if (!(inputStream instanceof CoyoteInputStream)) {
137                                // It's possible InputStream can be wrapped, preventing use of CoyoteInputStream
138                                return super.readFromInputStream();
139                        }
140                        boolean release = true;
141                        int capacity = this.bufferSize;
142                        DataBuffer dataBuffer = this.factory.allocateBuffer(capacity);
143                        try {
144                                ByteBuffer byteBuffer = dataBuffer.asByteBuffer(0, capacity);
145                                int read = ((CoyoteInputStream) inputStream).read(byteBuffer);
146                                logBytesRead(read);
147                                if (read > 0) {
148                                        dataBuffer.writePosition(read);
149                                        release = false;
150                                        return dataBuffer;
151                                }
152                                else if (read == -1) {
153                                        return EOF_BUFFER;
154                                }
155                                else {
156                                        return null;
157                                }
158                        }
159                        finally {
160                                if (release) {
161                                        DataBufferUtils.release(dataBuffer);
162                                }
163                        }
164                }
165        }
166
167
168        private static final class TomcatServerHttpResponse extends ServletServerHttpResponse {
169
170                private static final Field COYOTE_RESPONSE_FIELD;
171
172                static {
173                        Field field = ReflectionUtils.findField(ResponseFacade.class, "response");
174                        Assert.state(field != null, "Incompatible Tomcat implementation");
175                        ReflectionUtils.makeAccessible(field);
176                        COYOTE_RESPONSE_FIELD = field;
177                }
178
179                TomcatServerHttpResponse(HttpServletResponse response, AsyncContext context,
180                                DataBufferFactory factory, int bufferSize, ServletServerHttpRequest request) throws IOException {
181
182                        super(createTomcatHttpHeaders(response), response, context, factory, bufferSize, request);
183                }
184
185                private static HttpHeaders createTomcatHttpHeaders(HttpServletResponse response) {
186                        ResponseFacade responseFacade = getResponseFacade(response);
187                        org.apache.catalina.connector.Response connectorResponse = (org.apache.catalina.connector.Response)
188                                        ReflectionUtils.getField(COYOTE_RESPONSE_FIELD, responseFacade);
189                        Assert.state(connectorResponse != null, "No Tomcat connector response");
190                        Response tomcatResponse = connectorResponse.getCoyoteResponse();
191                        TomcatHeadersAdapter headers = new TomcatHeadersAdapter(tomcatResponse.getMimeHeaders());
192                        return new HttpHeaders(headers);
193                }
194
195                private static ResponseFacade getResponseFacade(HttpServletResponse response) {
196                        if (response instanceof ResponseFacade) {
197                                return (ResponseFacade) response;
198                        }
199                        else if (response instanceof HttpServletResponseWrapper) {
200                                HttpServletResponseWrapper wrapper = (HttpServletResponseWrapper) response;
201                                HttpServletResponse wrappedResponse = (HttpServletResponse) wrapper.getResponse();
202                                return getResponseFacade(wrappedResponse);
203                        }
204                        else {
205                                throw new IllegalArgumentException("Cannot convert [" + response.getClass() +
206                                                "] to org.apache.catalina.connector.ResponseFacade");
207                        }
208                }
209
210                @Override
211                protected void applyHeaders() {
212                        HttpServletResponse response = getNativeResponse();
213                        MediaType contentType = null;
214                        try {
215                                contentType = getHeaders().getContentType();
216                        }
217                        catch (Exception ex) {
218                                String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
219                                response.setContentType(rawContentType);
220                        }
221                        if (response.getContentType() == null && contentType != null) {
222                                response.setContentType(contentType.toString());
223                        }
224                        getHeaders().remove(HttpHeaders.CONTENT_TYPE);
225                        Charset charset = (contentType != null ? contentType.getCharset() : null);
226                        if (response.getCharacterEncoding() == null && charset != null) {
227                                response.setCharacterEncoding(charset.name());
228                        }
229                        long contentLength = getHeaders().getContentLength();
230                        if (contentLength != -1) {
231                                response.setContentLengthLong(contentLength);
232                        }
233                        getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
234                }
235
236                @Override
237                protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
238                        ByteBuffer input = dataBuffer.asByteBuffer();
239                        int len = input.remaining();
240                        ServletResponse response = getNativeResponse();
241                        ((CoyoteOutputStream) response.getOutputStream()).write(input);
242                        return len;
243                }
244        }
245
246}