序文
時には、我々は内部のインターセプトを流し要求または応答データにインターセプタを使用する必要があり、おそらくログ検索として、おそらくいくつかのチェックを行い、内部の情報の一部を読んで、私たちは、要求またはコールバックストリームを読み込むときデータの後に、次の2つの潜在的なピットがあるという事実があり、下流の流れの中、これらのデータは、再び消費することはできないでしょう。
ピット
次に、他の二つを使用して、使用され、すなわち一方が相互に排他的のgetInputStream()、getReader()のgetParameter()メソッドを、要求、データが取得されません。排他外側のgetInputStream()とgetReader()に加えて、一度だけ使用することができ、これはシングルスレッドのgetParameterで再利用することができます。
3つの方法は相互に排他的な理由です
org.apache.catalina.connector.Request方法は、我々はこれらの3つの方法の実装を見て、するjavax.servlet.http.HttpServletRequestインタフェースを実現します:
getInputStream
@Override
public ServletInputStream getInputStream() throws IOException {
if (usingReader) {
throw new IllegalStateException
(sm.getString("coyoteRequest.getInputStream.ise"));
}
usingInputStream = true;
if (inputStream == null) {
inputStream = new CoyoteInputStream(inputBuffer);
}
return inputStream;
}
复制代码
getReader
@Override
public BufferedReader getReader() throws IOException {
if (usingInputStream) {
throw new IllegalStateException
(sm.getString("coyoteRequest.getReader.ise"));
}
usingReader = true;
inputBuffer.checkConverter();
if (reader == null) {
reader = new CoyoteReader(inputBuffer);
}
return reader;
}
复制代码
まず見た目のgetInputStream()とgetReader()これらの2つの方法を見ることができ、それぞれusingReaderとusingInputStream兆候は流し読みに制限されている、これらの2つの方法の排他はよく理解されています。getParameterで下記をご覧ください()メソッドは、彼らとどのように相互に排他的です。
getParameter
@Override
public String getParameter(String name) {
// 只会解析一遍Parameter
if (!parametersParsed) {
parseParameters();
}
// 从coyoteRequest中获取参数
return coyoteRequest.getParameters().getParameter(name);
}
复制代码
一見すると見下し続け、ない心配を行い、相互に排他的ないないようです、私たちは見てみましょうする方法)(parseParametersを入力します(ソースコードの中間部分を直接見ることができます):
protected void parseParameters() {
//标识位,标志已经被解析过。
parametersParsed = true;
Parameters parameters = coyoteRequest.getParameters();
boolean success = false;
try {
// Set this every time in case limit has been changed via JMX
parameters.setLimit(getConnector().getMaxParameterCount());
// getCharacterEncoding() may have been overridden to search for
// hidden form field containing request encoding
String enc = getCharacterEncoding();
boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
if (enc != null) {
parameters.setEncoding(enc);
if (useBodyEncodingForURI) {
parameters.setQueryStringEncoding(enc);
}
} else {
parameters.setEncoding
(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
if (useBodyEncodingForURI) {
parameters.setQueryStringEncoding
(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
}
}
parameters.handleQueryParameters();
// 重点看这里:这里会判断是否有读取过流。如果有,则直接return。
if (usingInputStream || usingReader) {
success = true;
return;
}
if( !getConnector().isParseBodyMethod(getMethod()) ) {
success = true;
return;
}
String contentType = getContentType();
if (contentType == null) {
contentType = "";
}
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
contentType = contentType.substring(0, semicolon).trim();
} else {
contentType = contentType.trim();
}
if ("multipart/form-data".equals(contentType)) {
parseParts(false);
success = true;
return;
}
if (!("application/x-www-form-urlencoded".equals(contentType))) {
success = true;
return;
}
int len = getContentLength();
if (len > 0) {
int maxPostSize = connector.getMaxPostSize();
if ((maxPostSize > 0) && (len > maxPostSize)) {
Context context = getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.postTooLarge"));
}
checkSwallowInput();
return;
}
byte[] formData = null;
if (len < CACHED_POST_LEN) {
if (postData == null) {
postData = new byte[CACHED_POST_LEN];
}
formData = postData;
} else {
formData = new byte[len];
}
try {
if (readPostBody(formData, len) != len) {
return;
}
} catch (IOException e) {
// Client disconnect
Context context = getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.parseParameters"),
e);
}
return;
}
parameters.processParameters(formData, 0, len);
} else if ("chunked".equalsIgnoreCase(
coyoteRequest.getHeader("transfer-encoding"))) {
byte[] formData = null;
try {
formData = readChunkedPostBody();
} catch (IOException e) {
// Client disconnect or chunkedPostTooLarge error
Context context = getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.parseParameters"),
e);
}
return;
}
if (formData != null) {
parameters.processParameters(formData, 0, formData.length);
}
}
success = true;
} finally {
if (!success) {
parameters.setParseFailed(true);
}
}
}
复制代码
結果として、それはのgetParameter()メソッドを任意に読み取ることができない示します。なぜ、彼らはそれだけ一度読むことができますか?
その理由は、一度しか読むことができます
getInputStream()とgetReader()メソッドは、一度だけ読み出すことができ、及びのgetParameterは()のgetParameter()は、データストリームは、関連する、のLinkedHashMapに格納される解析する主な理由は、シングルスレッドで再利用可能ですパラメータは、ソースの)方法は、開始パラメータオブジェクトを生成するために見ることができる(上記parseParametersに、パッケージのクラスの内容を見ることができます。後続の読み出しデータは、オブジェクト内に存在します。getInputStream()とgetReader()メソッドがそうではないだろう、のgetInputStream()メソッドはCoyoteInputStream、getReaderを(返す)がCoyoteReaderを返しますが、CoyoteInputStreamはのInputStream、CoyoteReaderは、BufferedReaderの、のInputStreamとBufferedReaderはソースから読み取ったデータを参照してくださいレコードを継承しましたCoyoteInputStreamとCoyoteReaderデータにおける結果は一度しか読み取ることができ、リセット方法を達成していないので、座標読み出されたデータはリセットされません。
二つのピット
リクエスト、等、のgetOutputStream()とにgetWriter()メソッドに対する応答は相互に排他的であり、応答データの本体は、時間を消費することができます。
相互に排他的な理由
getOutputStream
@Override
public ServletOutputStream getOutputStream()
throws IOException {
if (usingWriter) {
throw new IllegalStateException
(sm.getString("coyoteResponse.getOutputStream.ise"));
}
usingOutputStream = true;
if (outputStream == null) {
outputStream = new CoyoteOutputStream(outputBuffer);
}
return outputStream;
}
复制代码
getWriter
@Override
public PrintWriter getWriter()
throws IOException {
if (usingOutputStream) {
throw new IllegalStateException
(sm.getString("coyoteResponse.getWriter.ise"));
}
if (ENFORCE_ENCODING_IN_GET_WRITER) {
setCharacterEncoding(getCharacterEncoding());
}
usingWriter = true;
outputBuffer.checkConverter();
if (writer == null) {
writer = new CoyoteWriter(outputBuffer);
}
return writer;
}
复制代码
その理由は、一度しか読むことができます
応答では、読取手段は、OutputStreamの本体からのデータが、またのOutputStreamとのInputStream同じ問題を再読み込み、トラフィックは一度しか読み取ることができ、それについて話をここでは起動しません。
ソリューション
春のライブラリーでは応答と要求が法相互に排他的な問題を読んで、繰り返すことはできません解決するために、それぞれ、二つのクラスContentCachingResponseWrapperとContentCachingRequestWrapperを提供します。私たちは、ラップに直接ContentCachingRequestWrapperパッケージ要求、ContentCachingResponseWrapperレスポンスを使用することができ、梱包後、これはので、それを読んだ後、データ・キャッシュとなり、その後、その上にデータを読み込むための要求または応答ストリームの時にデータ・ストリームを再書き込み。以下は、使用の簡単な例です:
ContentCachingResponseWrapper responseToCache = new ContentCachingResponseWrapper(response);
String responseBody = new String(responseToCache.getContentAsByteArray());
responseToCache.copyBodyToResponse();
复制代码
基本的な解決策のアイデアであるデータキャッシュの流れは、、のはgetContentAsByteArray()ライン上で、copyBodyToResponse()メソッドを中心に、ソースコードレベルで見てみましょう:
public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
private final FastByteArrayOutputStream content = new FastByteArrayOutputStream(1024);
private final ServletOutputStream outputStream = new ResponseServletOutputStream();
private PrintWriter writer;
private int statusCode = HttpServletResponse.SC_OK;
private Integer contentLength;
/**
* Create a new ContentCachingResponseWrapper for the given servlet response.
* @param response the original servlet response
*/
public ContentCachingResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public void setStatus(int sc) {
super.setStatus(sc);
this.statusCode = sc;
}
@SuppressWarnings("deprecation")
@Override
public void setStatus(int sc, String sm) {
super.setStatus(sc, sm);
this.statusCode = sc;
}
@Override
public void sendError(int sc) throws IOException {
copyBodyToResponse(false);
try {
super.sendError(sc);
}
catch (IllegalStateException ex) {
// Possibly on Tomcat when called too late: fall back to silent setStatus
super.setStatus(sc);
}
this.statusCode = sc;
}
@Override
@SuppressWarnings("deprecation")
public void sendError(int sc, String msg) throws IOException {
copyBodyToResponse(false);
try {
super.sendError(sc, msg);
}
catch (IllegalStateException ex) {
// Possibly on Tomcat when called too late: fall back to silent setStatus
super.setStatus(sc, msg);
}
this.statusCode = sc;
}
@Override
public void sendRedirect(String location) throws IOException {
copyBodyToResponse(false);
super.sendRedirect(location);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return this.outputStream;
}
@Override
public PrintWriter getWriter() throws IOException {
if (this.writer == null) {
String characterEncoding = getCharacterEncoding();
this.writer = (characterEncoding != null ? new ResponsePrintWriter(characterEncoding) :
new ResponsePrintWriter(WebUtils.DEFAULT_CHARACTER_ENCODING));
}
return this.writer;
}
@Override
public void flushBuffer() throws IOException {
// do not flush the underlying response as the content as not been copied to it yet
}
@Override
public void setContentLength(int len) {
if (len > this.content.size()) {
this.content.resize(len);
}
this.contentLength = len;
}
// Overrides Servlet 3.1 setContentLengthLong(long) at runtime
public void setContentLengthLong(long len) {
if (len > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Content-Length exceeds ContentCachingResponseWrapper's maximum (" +
Integer.MAX_VALUE + "): " + len);
}
int lenInt = (int) len;
if (lenInt > this.content.size()) {
this.content.resize(lenInt);
}
this.contentLength = lenInt;
}
@Override
public void setBufferSize(int size) {
if (size > this.content.size()) {
this.content.resize(size);
}
}
@Override
public void resetBuffer() {
this.content.reset();
}
@Override
public void reset() {
super.reset();
this.content.reset();
}
/**
* Return the status code as specified on the response.
*/
public int getStatusCode() {
return this.statusCode;
}
/**
* Return the cached response content as a byte array.
*/
public byte[] getContentAsByteArray() {
return this.content.toByteArray();
}
/**
* Return an {@link InputStream} to the cached content.
* @since 4.2
*/
public InputStream getContentInputStream() {
return this.content.getInputStream();
}
/**
* Return the current size of the cached content.
* @since 4.2
*/
public int getContentSize() {
return this.content.size();
}
/**
* Copy the complete cached body content to the response.
* @since 4.2
*/
public void copyBodyToResponse() throws IOException {
copyBodyToResponse(true);
}
/**
* Copy the cached body content to the response.
* @param complete whether to set a corresponding content length
* for the complete cached body content
* @since 4.2
*/
protected void copyBodyToResponse(boolean complete) throws IOException {
if (this.content.size() > 0) {
HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
if ((complete || this.contentLength != null) && !rawResponse.isCommitted()) {
rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
this.contentLength = null;
}
this.content.writeTo(rawResponse.getOutputStream());
this.content.reset();
if (complete) {
super.flushBuffer();
}
}
}
private class ResponseServletOutputStream extends ServletOutputStream {
@Override
public void write(int b) throws IOException {
content.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
content.write(b, off, len);
}
}
private class ResponsePrintWriter extends PrintWriter {
public ResponsePrintWriter(String characterEncoding) throws UnsupportedEncodingException {
super(new OutputStreamWriter(content, characterEncoding));
}
@Override
public void write(char buf[], int off, int len) {
super.write(buf, off, len);
super.flush();
}
@Override
public void write(String s, int off, int len) {
super.write(s, off, len);
super.flush();
}
@Override
public void write(int c) {
super.write(c);
super.flush();
}
}
}
复制代码
決済のContentCachingRequestWrapperの考え方がほとんどで、私はここでは起動しません、直接ソースコードを表示興味缶です。