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}