Skip to Content
Technical Articles

Logging incoming requests when creating short URLs

Based on my previous blog  – SICF on PI/PO , We may need to log incoming requests or process the incoming requests (e.g delete few header /prolog etc.) as per our needs before sending it to the adapter.

But the challenge here is you cannot in anyway touch the actual request –  the moment you do it – the stream will be all used up and nothing will be forwarded to Adapter resulting in an exception.

This challenge can be mitigated by making a copy of the request first and then using a wrapper to access the content.

The wrapper can also be used to read the content and ignore the request or else strip some unwanted data from it and then pass on the adapter .

 

Here after stripping the unwanted content and extracting the xml -it is posted to the  adapter.

The Loggers are used to log data in debug mode and can be viewed in  /nwa/logs

The resetInputStream changes the content to the xml and we forward the wrapper instead of the actual request.

 

 

If you are not OK with the content you can send back a custom response like here 406

Here I show you an example with OAGI format  which sends a Prolog sort of text  before actual xml.

The xml starts after the string “Payload=”

(PS: I am using fasterjackson library to convert objects into string)

The servlet with doPost

package com.mycompany;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.mycompany.logging.entity.LoggingRequest;
import com.mycompany.logging.wrapper.LoggingHttpServletRequestWrapper;
import com.mycompany.logging.wrapper.LoggingHttpServletResponseWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.sap.tc.logging.Configurator;
import com.sap.tc.logging.Log;
import com.sap.tc.logging.Severity;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;



/**
 * Servlet implementation class 
 */
public class oagi extends HttpServlet {
	
	private static final long serialVersionUID = 1L;
	  
	private static final com.sap.tc.logging.Location LOGGER = com.sap.tc.logging.Location.getLocation(oagi.class);
	ObjectMapper ObjectMapper = new ObjectMapper();
	
	/**
     * @see HttpServlet#HttpServlet()
     */
    public oagi() {
        super();
       
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		LOGGER.entering();
		
		response.getWriter().append("Served at: ").append(request.getContextPath());
		LOGGER.exiting();
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		LOGGER.entering("doPost");
			
		//PrintWriter out= response.getWriter();
		ServletContext web1 = getServletContext();
		ServletContext web2 = web1.getContext("/HttpAdapter");
	
		//send to adapter
		RequestDispatcher rd = 
		web2.getRequestDispatcher("/HttpMessageServlet?interfaceNamespace=http://OAGI/test&interface=SI_OrderRequest_OutS&senderService=BC_EU_Orders_OAGI&qos=BE");
		
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		LoggingHttpServletRequestWrapper requestWrapper = new LoggingHttpServletRequestWrapper(httpRequest);
		
		
			String content = requestWrapper.getContent();
			
			String	searchit = "PAYLOAD=";
			
			if(!content.isEmpty()) 
			{
				
				if(!(content.startsWith("<")))
				{
					LOGGER.debugT("original content : " + content);		
					int off = content.indexOf(searchit);
					if(off != -1)
					{
						off = off + searchit.length();
						content = content.substring(off);
						LOGGER.debugT("Stripped content : " + content);			
						requestWrapper.resetInputStream(content);
					    content = null;
				    
					    LOGGER.exiting("doPost" );
					    rd.forward(requestWrapper, response);
					}
					else
					{
						LOGGER.errorT("REQUEST: " + getRequestDescription(requestWrapper) +"Content:" +content );
					    
					    httpResponse.reset();
					    httpResponse.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); //send 406
					    httpResponse.setHeader("Location", requestWrapper.getLocalAddr());
					    return;	
					}
				}
				else 
				{
					content = null;
				    LOGGER.exiting("doPost" );
				    rd.forward(requestWrapper, response);
				}
				
				return;
			}
		
		
		
	}
	
	
	
	protected String getRequestDescription(LoggingHttpServletRequestWrapper requestWrapper) {
		LoggingRequest loggingRequest = new LoggingRequest();
		loggingRequest.setSender(requestWrapper.getRemoteAddr());
		loggingRequest.setMethod(requestWrapper.getMethod());
		
		loggingRequest.setUser(requestWrapper.getRemoteUser());
		loggingRequest.setParams(requestWrapper.isFormPost() ? null : requestWrapper.getParameters());
		loggingRequest.setHeaders(requestWrapper.getHeaders());
		
		//String content = requestWrapper.getContent();
		
		//loggingRequest.setBody(content);
		

		try {
			return ObjectMapper.writeValueAsString(loggingRequest);
		} catch (JsonProcessingException e) {
			LOGGER.warningT("Cannot serialize Request to JSON"+ e.getMessage());
			return null;
		}
	}

}

 

Sample Request Wrapper (most of the methods are not used)

package com.mycompany.logging.wrapper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import com.mycompany.logging.OAGIFilter;

public class LoggingHttpServletRequestWrapper extends HttpServletRequestWrapper {

	private static final com.sap.tc.logging.Location LOGGER = com.sap.tc.logging.Location.getLocation(LoggingHttpServletRequestWrapper.class);
	
	private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";

	private static final String METHOD_POST = "POST";

	private byte[] content;

	private final Map<String, String[]> parameterMap;

	private final HttpServletRequest delegate;
	private LoggingServletInputStream servletStream;
		
	
	
	public LoggingHttpServletRequestWrapper(HttpServletRequest request) {
		super(request);
		this.delegate = request;
		this.servletStream = new LoggingServletInputStream();
		
		if (isFormPost()) {
			this.parameterMap = request.getParameterMap();
		} else {
			this.parameterMap = Collections.emptyMap();
		}
	}

	public void resetInputStream(String content) {
		this.content = content.getBytes();
		
		LOGGER.debugT("content : " + new String(this.content));	
	    
		servletStream.is = new ByteArrayInputStream(content.getBytes());
	}
	
	@Override
	public ServletInputStream getInputStream() throws IOException {
		
		if (ArrayUtils.isEmpty(content)) {
			return delegate.getInputStream();
		}
		LOGGER.debugT("inputstream content : " + new String(this.content));	
		return new LoggingServletInputStream(content);
	}

	@Override
	public BufferedReader getReader() throws IOException {
		if (ArrayUtils.isEmpty(content)) {
			return delegate.getReader();
		}
		return new BufferedReader(new InputStreamReader(getInputStream()));
	}

	@Override
	public String getParameter(String name) {
		if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {
			return super.getParameter(name);
		}
		String[] values = this.parameterMap.get(name);
		if (values != null && values.length > 0) {
			return values[0];
		}
		return Arrays.toString(values);
	}

	@Override
	public Map<String, String[]> getParameterMap() {
		if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {
			return super.getParameterMap();
		}
		return this.parameterMap;
	}

	@Override
	public Enumeration<String> getParameterNames() {
		if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {
			return super.getParameterNames();
		}
		return new ParamNameEnumeration(this.parameterMap.keySet());
	}

	@Override
	public String[] getParameterValues(String name) {
		if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {
			return super.getParameterValues(name);
		}
		return this.parameterMap.get(name);
	}

	public String getContent() {
		try {
			if (this.parameterMap.isEmpty()) {
				content = IOUtils.toByteArray(delegate.getInputStream());
			} else {
				content = getContentFromParameterMap(this.parameterMap);
			}
			String requestEncoding = delegate.getCharacterEncoding();
			String normalizedContent = StringUtils.normalizeSpace(new String(content, requestEncoding != null ? requestEncoding : StandardCharsets.UTF_8.name()));
			return StringUtils.isBlank(normalizedContent) ? "[EMPTY]" : normalizedContent;
		} catch (IOException e) {
			throw new UncheckedIOException(e);
		}
	}
	
	
	

	private byte[] getContentFromParameterMap(Map<String, String[]> parameterMap) {
		return parameterMap.entrySet().stream().map(e -> {
			String[] value = e.getValue();
			return e.getKey() + "=" + (value.length == 1 ? value[0] : Arrays.toString(value));
		}).collect(Collectors.joining("&")).getBytes();
	}

	public Map<String, String> getHeaders() {
		Map<String, String> headers = new HashMap<>(0);
		Enumeration<String> headerNames = getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String headerName = headerNames.nextElement();
			if (headerName != null) {
				headers.put(headerName, getHeader(headerName));
			}
		}
		return headers;
	}

	public Map<String, String> getParameters() {
		return getParameterMap().entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> {
			String[] values = e.getValue();
			return values.length > 0 ? values[0] : "[EMPTY]";
		}));
	}

	public boolean isFormPost() {
		String contentType = getContentType();
		return (contentType != null && contentType.contains(FORM_CONTENT_TYPE) && METHOD_POST.equalsIgnoreCase(getMethod()));
	}

	
	private class ParamNameEnumeration implements Enumeration<String> {

		private final Iterator<String> iterator;

		private ParamNameEnumeration(Set<String> values) {
			this.iterator = values != null ? values.iterator() : Collections.emptyIterator();
		}

		@Override
		public boolean hasMoreElements() {
			return iterator.hasNext();
		}

		@Override
		public String nextElement() {
			return iterator.next();
		}
	}

	private class LoggingServletInputStream extends ServletInputStream {

		private  InputStream is;

		private LoggingServletInputStream() {
			
		}
		
		private LoggingServletInputStream(byte[] content) {
			this.is = new ByteArrayInputStream(content);
		}

		public boolean isFinished() {
			return true;
		}

		public boolean isReady() {
			return true;
		}

		

		@Override
		public int read() throws IOException {
			return this.is.read();
		}

		@Override
		public void close() throws IOException {
			super.close();
			is.close();
		}
	}
	
	
	 }


 

 

Logging Request

package com.mycompany.logging.entity;

import java.io.Serializable;
import java.util.Map;

public class LoggingRequest implements Serializable {

	private static final long serialVersionUID = -4702574169916528738L;

	private String sender;

	private String method;

	private String user;
	

	private Map<String, String> params;

	private Map<String, String> headers;

	private String body;

	public String getSender() {
		return sender;
	}

	public void setSender(String sender) {
		this.sender = sender;
	}

	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	public String getUser() {
		return user;
	}

	

	public void setUser(String user) {
		this.user = user;
	}

	public Map<String, String> getParams() {
		return params;
	}

	public void setParams(Map<String, String> params) {
		this.params = params;
	}

	public Map<String, String> getHeaders() {
		return headers;
	}

	public void setHeaders(Map<String, String> headers) {
		this.headers = headers;
	}

	public String getBody() {
		return body;
	}

	public void setBody(String body) {
		this.body = body;
	}
}

 

Example : Error logged in /nwa/logs when payload had some irregular data:

 

Sample wrong payload

 

 

Sample unacceptable response

Here the request processing takes place in the doPost method itself – alternatively you can create a Filter in the project and use that to process the request.

Hope this would be helpful !

 

Be the first to leave a comment
You must be Logged on to comment or reply to a post.