zenn.skin 무료버전 배포중!
자세히보기

Web/Spring

[Spring Web 6.1] DispatcherServlet 뜯어보기 - Multipart & ExceptionHandling

koosco! 2024. 10. 7. 19:22

Multipart

checkMultipart

HTTP 요청이 멀티파트 요청인지 확인하고, 멀티파트 요청인 경우 이를 적절하게 처리하는 역할을 한다.

멀티파트 요청은 파일 업로드와 관련된 요청을 처리할 때 사용된다.

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
                this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        } else if (hasMultipartException(request)) {
            this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering");
        } else {
            try {
                return this.multipartResolver.resolveMultipart(request);
            } catch (MultipartException var3) {
                if (request.getAttribute("jakarta.servlet.error.exception") == null) {
                    throw var3;
                }
            }

            this.logger.debug("Multipart resolution failed for error dispatch", var3);
        }
    }

    return request;
}

this.multipartResolver.resolveMultipart(request)는 멀티 파트로 변환하는 메서드로 별도의 multipartResolver를 설정하지 않으면 StandardServletMultipartResolver가 호출되고, StandardMultipartHttpServletRequest가 반한된다.

public class StandardServletMultipartResolver implements MultipartResolver {
    public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
        return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
    }
}

 

StandardMultipartHttpServletRequest

StandardMultipartHttpServletRequest에 대해 간단하게 살펴보자.

request를 생성자로 받으면 parseRequest 메서드가 호출된며, 요청에 있는 parts들을 multipartFiles에 저장하게 된다.

multipartFiles는 상속받은 AbstractMultipartHttpServletRequest에 있는데, 다음과 같이 key-value 구조로 되어있는 것을 알 수 있다.

@Nullable
private MultiValueMap<String, MultipartFile> multipartFiles;
public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {
    @Nullable
    private Set<String> multipartParameterNames;

    public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
        super(request);
        if (!lazyParsing) {
            this.parseRequest(request);
        }

    }

    private void parseRequest(HttpServletRequest request) {
        try {
            Collection<Part> parts = request.getParts();
            this.multipartParameterNames = new LinkedHashSet(parts.size());
            MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap(parts.size());
            Iterator var4 = parts.iterator();

            while(var4.hasNext()) {
                Part part = (Part)var4.next();
                String headerValue = part.getHeader("Content-Disposition");
                ContentDisposition disposition = ContentDisposition.parse(headerValue);
                String filename = disposition.getFilename();
                if (filename != null) {
                    files.add(part.getName(), new StandardMultipartFile(part, filename));
                } else {
                    this.multipartParameterNames.add(part.getName());
                }
            }

            this.setMultipartFiles(files);
        } catch (Throwable var9) {
            this.handleParseFailure(var9);
        }

    }
	
    protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
        this.multipartFiles = new LinkedMultiValueMap(Collections.unmodifiableMap(multipartFiles));
    }
}

코드 중에 ContentDisposition이 있는데, ContentDisposition은 파일 다운로드 또는 파일 첨부와 같은 상황에서 Content-Disposition 헤더를 설정하거나 파싱할 때 사용되는 클래스로, 여기서는 filename을 얻기 위해 사용되었다.

 

cleanupMultipart

protected void cleanupMultipart(HttpServletRequest request) {
    if (this.multipartResolver != null) {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
        if (multipartRequest != null) {
            this.multipartResolver.cleanupMultipart(multipartRequest);
        }
    }

}

cleanupMultipart는 multipartRequest가 있을 때, multipartResolver에게 cleanupMultipart를 요청한다.

해당 메서드에서는 요청이 AbstractMultipartHttpServletRequest 인스턴스인지를 확인하고, 저장되었던 part를 삭제하는 역할을 수행한다.

public void cleanupMultipart(MultipartHttpServletRequest request) {
    if (request instanceof AbstractMultipartHttpServletRequest abstractMultipartHttpServletRequest) {
        if (!abstractMultipartHttpServletRequest.isResolved()) {
            return;
        }
    }

    try {
        Iterator var3 = request.getParts().iterator();

        while(var3.hasNext()) {
            Part part = (Part)var3.next();
            if (request.getFile(part.getName()) != null) {
                part.delete();
            }
        }
    } catch (Throwable var5) {
        LogFactory.getLog(this.getClass()).warn("Failed to perform cleanup of multipart items", var5);
    }

}

 

Part

Part 인터페이스는 Java Servlet API에서 제공하는 인터페이스로, 멀티파트(form-data) 요청을 처리하기 위해 사용된다. 주로 파일 업로드와 관련된 기능을 제공하며, 클라이언트가 전송한 파일이나 데이터의 세부 정보를 다룰 때 사용하는 인터페이스이다.

Part의 구현체는 ApplicationPart와 MockPart가 있는데, test를 제외하고는 ApplicationPart를 사용하게 된다.

인터페이스를 보면 알 수 있듯이, 파일의 내용과 메타 데이터를 저장하는 것을 알 수 있다.

public interface Part {
    InputStream getInputStream() throws IOException;

    String getContentType();

    String getName();

    String getSubmittedFileName();

    long getSize();

    void write(String var1) throws IOException;

    void delete() throws IOException;

    String getHeader(String var1);

    Collection<String> getHeaders(String var1);

    Collection<String> getHeaderNames();
}

'Web/Spring'의 다른글

  • 현재글 [Spring Web 6.1] DispatcherServlet 뜯어보기 - Multipart & ExceptionHandling

관련글