그냥 호기심에 시작하게 되었다.
이 게시물에는 크게 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
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]}
이 글을 참고해서 작성된 코드다.
//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 ...
}
}
아직 이 코드의 정확한 의미를 파악하지 못했다...
만약 위 코드가 왜 필요한지 좀 더 알게 되면 아래에 추가 작성하겠다.
//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);
}
@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);
}
안녕하세요
// new UrlResource("file:" + "D:/uploadFile/123.jpg"); 대체가능
Resource resource1 = new FileSystemResource("D:/uploadFile/123.jpg");
이 부분에서 body에 add되는 object가 제대로 들어간거 맞을까요??ㅠ