[Spring MVC] MultipartFile을 서버 또는 Java 코드에서 전송하는 방법

식빵·2021년 12월 21일
2

Spring Lab

목록 보기
3/33
post-thumbnail

🍀 개요

그냥 호기심에 시작하게 되었다.

이 게시물에는 크게 2 가지 흐름을 생각할 것이다.

1. 내 컴퓨터(혹은 서버)에 있는 파일을 Rest API 에 전송하는 방식

2. Client로부터 받은 MutlipartFile 을 다시 다른 Rest API에 전송하는 방식
(앞으로는 짧게 proxy 방식이라고 부르겠다)

spring-mvc에 있는 org.springframework.web.client.RestTemplate를 주로 사용할 것이다.

실습을 위해서 가볍게 Spring boot 프로젝트를 하나 생성하고 의존성은 아래와 같이 줬다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

그리고 코드에서 import 시 헷갈릴 수 있는 것들은 코드 상단에 주석으로 import 문을 작성해놨다.

import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.core.io.Resource
import org.springframework.core.io.UrlResource
import org.springframework.core.io.FileSystemResource
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.client.SimpleClientHttpRequestFactory;



🍀 내 컴퓨터(혹은 서버)에 있는 파일 전송하기


☘ 파일을 전송하는 코드 (= 클라이언트)

private static final RestTemplate REST_TEMPLATE;

static {
    // RestTemplate 기본 설정을 위한 Factory 생성
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(3000);
    factory.setReadTimeout(3000);
    factory.setBufferRequestBody(false); // 파일 전송은 이 설정을 꼭 해주자.
    REST_TEMPLATE = new RestTemplate(factory);
}

public static void main(String[] args) {
    LinkedMultiValueMap<String, Object> body = new LinkedMultiValueMap<>();

    // 주의! spring의 org.springframework.core.io.Resource 클래스 타입을 사용함!
    // java.io.File 사용하면 안된다!!!
    // new UrlResource("file:" + "D:/uploadFile/123.jpg"); 대체가능
    Resource resource1 = new FileSystemResource("D:/uploadFile/123.jpg");
    
    // new UrlResource("file:" + "D:/uploadFile/testImg.jpg");
    Resource resource2 = new FileSystemResource("D:/uploadFile/testImg.jpg");

    body.add("files", resource1);
    body.add("files", resource2);
    // body.add("wow", "this is amazing"); // 문자열도 전송 가능하다 😁

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);

    HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity 
    	= new HttpEntity<>(body, headers);

    String serverUrl = "http://localhost:8080/test/multipart";

    ResponseEntity<JsonNode> postForEntity 
    	= REST_TEMPLATE.postForEntity(serverUrl, httpEntity, JsonNode.class);

    System.out.println(postForEntity.getStatusCodeValue());
    System.out.println(postForEntity.getBody());
    System.out.println(postForEntity.getHeaders());
}

☘ 파일 전송 요청을 수신하는 Controller Method

@Controller
public class TestController {
    
  @PostMapping("/test/multipart")
  public ResponseEntity<Map<String, String>> testMultipart(List<MultipartFile> files) {
       files.forEach(file -> {
           System.out.println(file.getContentType());
           System.out.println(file.getOriginalFilename());
       });
       HashMap<String, String> resultMap = new HashMap<>();
       resultMap.put("result", "success");
       return ResponseEntity.ok(resultMap);
  }
    
}

출력 결과

200
{"result":"success"}
{Set-Cookie=[JSESSIONID=726F0006FE0BAC9E0CE513B4B83A8169; Path=/; HttpOnly], Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Tue, 21 Dec 2021 02:30:27 GMT], Keep-Alive=[timeout=20], Connection=[keep-alive]}



🍀 proxy 방식

이 글을 참고해서 작성된 코드다.


☘ 필요한 클래스 작성

//import java.io.IOException;
//import java.io.InputStream;
//import org.springframework.core.io.InputStreamResource;

class MultipartInputStreamFileResource extends InputStreamResource {

    private final String filename;

    MultipartInputStreamFileResource(InputStream inputStream, String filename) {
        super(inputStream);
        this.filename = filename;
    }

    @Override
    public String getFilename() {
        return this.filename;
    }

    @Override
    public long contentLength() throws IOException {
        return -1; // we do not want to generally read the whole stream into memory ...
    }
}

아직 이 코드의 정확한 의미를 파악하지 못했다...
만약 위 코드가 왜 필요한지 좀 더 알게 되면 아래에 추가 작성하겠다.


☘ client에게서 파일을 받고 다시 전송하는 Controller Method

//import org.springframework.web.client.HttpStatusCodeException;
//import com.fasterxml.jackson.databind.JsonNode;
//import org.springframework.http.HttpEntity;
//import org.springframework.http.HttpHeaders;
//import org.springframework.http.client.SimpleClientHttpRequestFactory;

private static final RestTemplate REST_TEMPLATE;


static {
    // RestTemplate 기본 설정을 위한 Factory 생성
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(3000);
    factory.setReadTimeout(3000);
    factory.setBufferRequestBody(false); // 파일 전송은 이 설정을 꼭 해주자.
    REST_TEMPLATE = new RestTemplate(factory);
}

@RequestMapping(value = "/proxyUpload", method = RequestMethod.POST) 
public ResponseEntity<?> uploadImages(List<MultipartFile> files) throws IOException {

    LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
    JsonNode response;
    HttpStatus httpStatus = HttpStatus.CREATED;

    try {
        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                map.add("files", 
                new MultipartInputStreamFileResource(file.getInputStream(),
                file.getOriginalFilename()));
                // Spring 5.1 이상의 버전을 쓴다면 
                // map.add("files", file.getResource()); 로 변경 가능합니다!
                // 즉, MultipartInputStreamFileResource 자체를 사용 안해도 된다는 의미죠!
            }
        }
	//map.add("stringValue", "string!"); // 문자열도 가능
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        String url = "http://localhost:8080/test_rest_template_get";

        HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity 
        	= new HttpEntity<>(map, headers);
        response = restTemplate.postForObject(url, requestEntity, JsonNode.class);

    } catch (HttpStatusCodeException e) {
        HttpStatus errorHttpStatus = HttpStatus.valueOf(e.getStatusCode().value());
        String errorResponse = e.getResponseBodyAsString();
        return new ResponseEntity<>(errorResponse, errorHttpStatus);
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }

    return new ResponseEntity<>(response, httpStatus);
}

☘ 재전송된 Multipartfile 을 받는 Controller method

@RequestMapping("/test_rest_template_get")
@ResponseBody
public String test2(List<MultipartFile> files) {
    files.forEach(file -> {
        System.out.println(file.getContentType());
        System.out.println(file.getOriginalFilename());
    });
    HashMap<String, String> resultMap = new HashMap<>();
    resultMap.put("result", "success");
    return ResponseEntity.ok(resultMap);
}



📝 참고 링크

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

2개의 댓글

comment-user-thumbnail
2023년 5월 12일

안녕하세요
// new UrlResource("file:" + "D:/uploadFile/123.jpg"); 대체가능
Resource resource1 = new FileSystemResource("D:/uploadFile/123.jpg");

// new UrlResource("file:" + "D:/uploadFile/testImg.jpg");
Resource resource2 = new FileSystemResource("D:/uploadFile/testImg.jpg");

body.add("files", urlResource);
body.add("files", urlResource2);

이 부분에서 body에 add되는 object가 제대로 들어간거 맞을까요??ㅠ

1개의 답글