net.bull.javamelody.PayloadNameRequestWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javamelody-core Show documentation
Show all versions of javamelody-core Show documentation
Monitoring of JavaEE applications
/*
* Copyright 2008-2019 by Emeric Vernat
*
* This file is part of Java Melody.
*
* Licensed 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 net.bull.javamelody;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.regex.Pattern;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import net.bull.javamelody.internal.common.LOG;
//20091201 dhartford GWTRequestWrapper
//20100519 dhartford adjustments for UTF-8, however did not have an impact so removed.
//20100520 dhartford adjustments for reader/inputstream.
//20110206 evernat refactoring
//20131111 roy.paterson SOAP request wrapper
//20131111 evernat refactoring
/**
* Simple Wrapper class to inspect payload for name.
* @author dhartford, roy.paterson, evernat
*/
public class PayloadNameRequestWrapper extends HttpServletRequestWrapper {
private static final Pattern GWT_RPC_SEPARATOR_CHAR_PATTERN = Pattern
.compile(Pattern.quote("|"));
/**
* Name of request, or null if we don't know based on payload @null
*/
private String name;
/**
* Type of request if name != null, or null if we don't know based on the payload @null
*/
private String requestType;
private BufferedInputStream bufferedInputStream;
private ServletInputStream inputStream;
private BufferedReader reader;
/**
* Constructor.
* @param request the original HttpServletRequest
*/
public PayloadNameRequestWrapper(HttpServletRequest request) {
super(request);
}
protected void initialize() throws IOException {
//name on a best-effort basis
name = null;
requestType = null;
final HttpServletRequest request = (HttpServletRequest) getRequest();
final String contentType = request.getContentType();
if (contentType == null) {
//don't know how to handle this content type
return;
}
if (!"POST".equalsIgnoreCase(request.getMethod())) {
//no payload
return;
}
//Try look for name in payload on a best-effort basis...
try {
if (contentType.startsWith("text/x-gwt-rpc")) {
//parse GWT-RPC method name
name = parseGwtRpcMethodName(getBufferedInputStream(), getCharacterEncoding());
requestType = "GWT-RPC";
} else if (contentType.startsWith("application/soap+xml") //SOAP 1.2
|| contentType.startsWith("text/xml") //SOAP 1.1
&& request.getHeader("SOAPAction") != null) {
//parse SOAP method name
name = parseSoapMethodName(getBufferedInputStream(), getCharacterEncoding());
requestType = "SOAP";
} else {
//don't know how to name this request based on payload
//(don't parse if text/xml for XML-RPC, because it is obsolete)
name = null;
requestType = null;
}
} catch (final Exception e) {
LOG.debug("Error trying to parse payload content for request name", e);
//best-effort - couldn't figure it out
name = null;
requestType = null;
} finally {
//reset stream so application is unaffected
resetBufferedInputStream();
}
}
protected BufferedInputStream getBufferedInputStream() throws IOException {
if (bufferedInputStream == null) {
//workaround Tomcat issue with form POSTs
//see http://stackoverflow.com/questions/18489399/read-httpservletrequests-post-body-and-then-call-getparameter-in-tomcat
final ServletRequest request = getRequest();
request.getParameterMap();
//buffer the payload so we can inspect it
bufferedInputStream = new BufferedInputStream(request.getInputStream());
// and mark to allow the stream to be reset
bufferedInputStream.mark(Integer.MAX_VALUE);
}
return bufferedInputStream;
}
protected void resetBufferedInputStream() throws IOException {
if (bufferedInputStream != null) {
bufferedInputStream.reset();
}
}
/**
* Try to parse GWT-RPC method name from request body stream. Does not close the stream.
*
* @param stream GWT-RPC request body stream @nonnull
* @param charEncoding character encoding of stream, or null for platform default @null
* @return GWT-RPC method name, or null if unable to parse @null
*/
@SuppressWarnings("resource")
private static String parseGwtRpcMethodName(InputStream stream, String charEncoding) {
//commented out code uses GWT-user library for a more 'proper' approach.
//GWT-user library approach is more future-proof, but requires more dependency management.
// RPCRequest decodeRequest = RPC.decodeRequest(readLine);
// gwtmethodname = decodeRequest.getMethod().getName();
try {
final Scanner scanner;
if (charEncoding == null) {
scanner = new Scanner(stream);
} else {
scanner = new Scanner(stream, charEncoding);
}
scanner.useDelimiter(GWT_RPC_SEPARATOR_CHAR_PATTERN); //AbstractSerializationStream.RPC_SEPARATOR_CHAR
//AbstractSerializationStreamReader.prepareToRead(...)
scanner.next(); //stream version number
scanner.next(); //flags
//ServerSerializationStreamReader.deserializeStringTable()
scanner.next(); //type name count
//ServerSerializationStreamReader.preapreToRead(...)
scanner.next(); //module base URL
scanner.next(); //strong name
//RPC.decodeRequest(...)
scanner.next(); //service interface name
return "." + scanner.next(); //service method name
//note we don't close the scanner because we don't want to close the underlying stream
} catch (final NoSuchElementException e) {
LOG.debug("Unable to parse GWT-RPC request", e);
//code above is best-effort - we were unable to parse GWT payload so
//treat as a normal HTTP request
return null;
}
}
/**
* Scan xml for tag child of the current element
*
* @param reader reader, must be at "start element" @nonnull
* @param tagName name of child tag to find @nonnull
* @return if found tag
* @throws XMLStreamException on error
*/
static boolean scanForChildTag(XMLStreamReader reader, String tagName)
throws XMLStreamException {
assert reader.isStartElement();
int level = -1;
while (reader.hasNext()) {
//keep track of level so we only search children, not descendants
if (reader.isStartElement()) {
level++;
} else if (reader.isEndElement()) {
level--;
}
if (level < 0) {
//end parent tag - no more children
break;
}
reader.next();
if (level == 0 && reader.isStartElement() && reader.getLocalName().equals(tagName)) {
return true; //found
}
}
return false; //got to end of parent element and not found
}
/**
* Try to parse SOAP method name from request body stream. Does not close the stream.
*
* @param stream SOAP request body stream @nonnull
* @param charEncoding character encoding of stream, or null for platform default @null
* @return SOAP method name, or null if unable to parse @null
*/
private static String parseSoapMethodName(InputStream stream, String charEncoding) {
try {
// newInstance() et pas newFactory() pour java 1.5 (issue 367)
final XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // disable DTDs entirely for that factory
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); // disable external entities
final XMLStreamReader xmlReader;
if (charEncoding != null) {
xmlReader = factory.createXMLStreamReader(stream, charEncoding);
} else {
xmlReader = factory.createXMLStreamReader(stream);
}
//best-effort parsing
//start document, go to first tag
xmlReader.nextTag();
//expect first tag to be "Envelope"
if (!"Envelope".equals(xmlReader.getLocalName())) {
LOG.debug("Unexpected first tag of SOAP request: '" + xmlReader.getLocalName()
+ "' (expected 'Envelope')");
return null; //failed
}
//scan for body tag
if (!scanForChildTag(xmlReader, "Body")) {
LOG.debug("Unable to find SOAP 'Body' tag");
return null; //failed
}
xmlReader.nextTag();
//tag is method name
return "." + xmlReader.getLocalName();
} catch (final XMLStreamException e) {
LOG.debug("Unable to parse SOAP request", e);
//failed
return null;
}
}
/** {@inheritDoc} */
@Override
public BufferedReader getReader() throws IOException {
if (bufferedInputStream == null) {
return super.getReader();
}
if (reader == null) {
// use character encoding as said in the API
final String characterEncoding = this.getCharacterEncoding();
if (characterEncoding == null) {
reader = new BufferedReader(new InputStreamReader(this.getInputStream()));
} else {
reader = new BufferedReader(
new InputStreamReader(this.getInputStream(), characterEncoding));
}
}
return reader;
}
/** {@inheritDoc} */
@Override
public ServletInputStream getInputStream() throws IOException {
final ServletInputStream requestInputStream = super.getInputStream();
if (bufferedInputStream == null) {
return requestInputStream;
}
if (inputStream == null) {
final BufferedInputStream myBufferedInputStream = bufferedInputStream;
//CHECKSTYLE:OFF
inputStream = new ServletInputStream() {
//CHECKSTYLE:ON
@Override
public int read() throws IOException {
return myBufferedInputStream.read();
}
@Override
public boolean isFinished() {
return requestInputStream.isFinished();
}
@Override
public boolean isReady() {
return requestInputStream.isReady();
}
@Override
public void setReadListener(ReadListener readListener) {
requestInputStream.setReadListener(readListener);
}
};
}
return inputStream;
}
/**
* @return name of request, or null if we can't figure out a good name based on
* the request payload @null
*/
public String getPayloadRequestName() {
return name;
}
/**
* Get type of request. If {@link #getPayloadRequestName()} returns non-null then
* this method also returns non-null.
*
* @return type of request if or null if don't know @null
*/
public String getPayloadRequestType() {
return requestType;
}
}