위 그림과 같이 filter의 경우 request와 reponse에 대해서 처리를 해주지만 interceptor처럼 prehandle/posthandle/afterCompletion 와 같이 실행되는 시점을 나눠서 처리할 수가 없다.
그래서 나는 doFilter() 메쏘드가 request와 response 시점에 두번 실행되는 것으로 생각했다.
하지만 debug모드를 이용하여 테스트 한 결과 chain.doFilter(,); 메쏘드의 코드 라인 기준으로 먼저 선언된 코드는 request에 실행되고 chain.doFilter(,); 메쏘드 이후 작성된 코드는 response처리 시점에 실행되는 것으로 확인 되었다.
filter 자체가 쓰레드 기반으로 실행이 되고 dofilter기점으로 해당 쓰레드는 sleep상태를 유지하다가 servelt이 실행되고 결과를 리턴하여 dispatcherServlet이 filter를 깨워주는 형태로 WAS가 처리해주는 것으로 예상된다.
- 주의점 exception 발생시 Interceptor과 filter 를 통하지 않으므로 참고
해당 필터를 통해서 HttpRequest에서 getInputStream을 하게 되면 해당 Request에서 inputStream의 데이타 없어지므로 주의 해야 한다.
HttpServletRequest의 InputStream은 한 번 읽으면 다시 읽을 수 없습니다. 톰캣 개발자님들께서 친히 막아두셨기 때문이죠! 만약 Interceptor나 Filter에서 InputStream을 읽게되면, 이후 Spring이 Converter를 이용해 Json 데이터를 바인딩 처리할 때 아래와 같은 에러를 만날 수 있습니다. 불쌍한 Spring이 이미 읽어버린 InputStream을 다시 읽으려고 시도하다가 슬픈 에러를 뱉어내는거죠.
java.lang.IllegalStateException: getReader() has already been called for this request
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Stream closed; nested exception is java
기본적으로 HttpServletRequestWrapper을 상속 받아서 처리를 하나 Multipart처리를 위하여 AbstractMultipartHttpServletRequest 를 상속 받고 StandardMultipartHttpServletRequest 클래스의 Multipart 처리 부분의 코드를 그대로 copy해서 구현한다.
HttpRequestWrapper.java
/**
* HttpRequestWrapper
* Multipart 처리를 위한 AbstractMultipartHttpServletRequest 상속으로 처리 변경
* @author mike
* @date 2017. 8. 8.
* @version
*/
public class HttpRequestWrapper extends AbstractMultipartHttpServletRequest {
private byte[] bodyData;
public HttpRequestWrapper(HttpServletRequest request) throws IOException {
this(request, false);
InputStream is = super.getInputStream();
bodyData = IOUtils.toByteArray(is);
}
public HttpRequestWrapper(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
super(request);
if (!lazyParsing) {
parseRequest(request);
}
}
//InputStream에 대해서 지속적으로 사용할 수 있도록 수정
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bis = new ByteArrayInputStream(bodyData);
return new ServletInputImpl(bis);
}
//Multipart 처리 부분
private static final String CONTENT_DISPOSITION = "content-disposition";
private static final String FILENAME_KEY = "filename=";
private Set<String> multipartParameterNames;
private void parseRequest(HttpServletRequest request) {
try {
Collection<Part> parts = request.getParts();
this.multipartParameterNames = new LinkedHashSet<String>(parts.size());
MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());
for (Part part : parts) {
String filename = extractFilename(part.getHeader(CONTENT_DISPOSITION));
if (filename != null) {
files.add(part.getName(), new StandardMultipartFile(part, filename));
}
else {
this.multipartParameterNames.add(part.getName());
}
}
setMultipartFiles(files);
}
catch (Exception ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
}
private String extractFilename(String contentDisposition) {
if (contentDisposition == null) {
return null;
}
// TODO: can only handle the typical case at the moment
int startIndex = contentDisposition.indexOf(FILENAME_KEY);
if (startIndex == -1) {
return null;
}
String filename = contentDisposition.substring(startIndex + FILENAME_KEY.length());
if (filename.startsWith("\"")) {
int endIndex = filename.indexOf("\"", 1);
if (endIndex != -1) {
return filename.substring(1, endIndex);
}
}
else {
int endIndex = filename.indexOf(";");
if (endIndex != -1) {
return filename.substring(0, endIndex);
}
}
return filename;
}
@Override
protected void initializeMultipart() {
parseRequest(getRequest());
}
@Override
public Enumeration<String> getParameterNames() {
if (this.multipartParameterNames == null) {
initializeMultipart();
}
if (this.multipartParameterNames.isEmpty()) {
return super.getParameterNames();
}
// Servlet 3.0 getParameterNames() not guaranteed to include multipart form items
// (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
Set<String> paramNames = new LinkedHashSet<String>();
Enumeration<String> paramEnum = super.getParameterNames();
while (paramEnum.hasMoreElements()) {
paramNames.add(paramEnum.nextElement());
}
paramNames.addAll(this.multipartParameterNames);
return Collections.enumeration(paramNames);
}
@Override
public Map<String, String[]> getParameterMap() {
if (this.multipartParameterNames == null) {
initializeMultipart();
}
if (this.multipartParameterNames.isEmpty()) {
return super.getParameterMap();
}
// Servlet 3.0 getParameterMap() not guaranteed to include multipart form items
// (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
Map<String, String[]> paramMap = new LinkedHashMap<String, String[]>();
paramMap.putAll(super.getParameterMap());
for (String paramName : this.multipartParameterNames) {
if (!paramMap.containsKey(paramName)) {
paramMap.put(paramName, getParameterValues(paramName));
}
}
return paramMap;
}
@Override
public String getMultipartContentType(String paramOrFileName) {
try {
Part part = getPart(paramOrFileName);
return (part != null ? part.getContentType() : null);
}
catch (Exception ex) {
throw new MultipartException("Could not access multipart servlet request", ex);
}
}
@Override
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
try {
Part part = getPart(paramOrFileName);
if (part != null) {
HttpHeaders headers = new HttpHeaders();
for (String headerName : part.getHeaderNames()) {
headers.put(headerName, new ArrayList<String>(part.getHeaders(headerName)));
}
return headers;
}
else {
return null;
}
}
catch (Exception ex) {
throw new MultipartException("Could not access multipart servlet request", ex);
}
}
/**
* Spring MultipartFile adapter, wrapping a Servlet 3.0 Part object.
*/
private static class StandardMultipartFile implements MultipartFile {
private final Part part;
private final String filename;
public StandardMultipartFile(Part part, String filename) {
this.part = part;
this.filename = filename;
}
@Override
public String getName() {
return this.part.getName();
}
@Override
public String getOriginalFilename() {
return this.filename;
}
@Override
public String getContentType() {
return this.part.getContentType();
}
@Override
public boolean isEmpty() {
return (this.part.getSize() == 0);
}
@Override
public long getSize() {
return this.part.getSize();
}
@Override
public byte[] getBytes() throws IOException {
return FileCopyUtils.copyToByteArray(this.part.getInputStream());
}
@Override
public InputStream getInputStream() throws IOException {
return this.part.getInputStream();
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
this.part.write(dest.getPath());
}
}
}
class ServletInputImpl extends ServletInputStream {
private InputStream is;
public ServletInputImpl(InputStream bis) {
is = bis;
}
@Override
public int read() throws IOException {
return is.read();
}
@Override
public int read(byte[] b) throws IOException {
return is.read(b);
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setReadListener(ReadListener arg0) {
// TODO Auto-generated method stub
}