[Angular 2+] multipart 보낼 시 헤더에 Content-type 추가하기





작업환경: Angular 4



기존 글에 이어 수정해야 할 부분이 있어 추가로 글을 쓰게 되었다.

이전글
 - [Angular 2+] Form Submit 할 시 파일 포함하기 (multipart) - File Upload






Form Submit 할 시 문제가 되었던 부분은 아래와 같다.

Request 내용을 확인해보니 각각의 파라메터에 Content-Type이 생성되지 않고 Content-Disposition이 붙어서 Request가 발생을 했다.
그러다보니 서버사이드에서 사용하는 Spring의 객체에 데이터가 들어가지 않게 되었다.



스펙을 보니 FormData Object 에 파라메터를 추가 시 Content-Disposition 헤더가 붙는다고 기술되어 있다.
https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects




서버사이드 Spring 에서 파일과 같이 객체에 데이터를 받기위해 아래와 같이 작업을 했다.

1004lucifer

multipart-form.service.ts
import {Injectable} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {Headers, Http, RequestMethod, Response} from '@angular/http';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class MultipartFormService {

    constructor(
        private http: Http
    ) {
    }

    /**
     * Object / File 객체를 Multipart 로 묶어서 서버에 전송한다.
     * @param {requestMethod} RequestMethod
     * @param {string} url
     * @param {Object} jsonObject
     * @param {Array<{name: string; file: File}>} files
     * @returns {Observable<Response>}
     */
    sendMultipartJsonObject(requestMethod: RequestMethod, url: string, jsonObject: Object, files?: Array<{name: string, file: File}>): Observable<Response> {

        const formData = new FormData();

        let blob;
        for (const key in jsonObject) {
            if (jsonObject.hasOwnProperty(key)) {
                blob = new Blob([jsonObject[key]], {type: 'application/json; charset=UTF-8'});
                formData.append(key, blob);
            }
        }

        if (files) {
            files
                .filter((file) => file.name != null && file.file != null)  // 정상적이지 않은 객체 Filter
                .forEach((file) => {
                    formData.append(file.name, file.file);
                });
        }

        const headers = new Headers({'enctype': 'multipart/form-data'});
        return requestMethod === RequestMethod.Post ? this.http.post(url, formData, { headers }) :
            requestMethod === RequestMethod.Put ? this.http.put(url, formData, { headers }) : null;
    }

    extractFile(event) {
        const files = event.target.files || event.srcElement.files;
        return files.length === 0 ? null : files[0];
    }
    extractFileName(event) {
        const file = this.extractFile(event);
        return file ? file.name : '';
    }
}

1004lucifer

devices.component.ts
import {Component, Input, OnInit} from '@angular/core';
import {RequestMethod} from '@angular/http';
import {MultipartFormService} from '../../shared/multipart-form/multipart-form.service';

@Component({
    selector: 'jhi-devices',
    templateUrl: './devices.component.html',
    styles: []
})
export class DevicesComponent implements OnInit {

    private readonly apiUrl = '/api/urlname';

    isNew: boolean;

    deviceInfo: Device;

    file1: File;
    file2: File;

    constructor(
        private multipartFormService: MultipartFormService
    ) {
    }

    onSubmit() {
        this.multipartFormService.sendMultipartJsonObject(
            this.isNew ? RequestMethod.Post : RequestMethod.Put,
            this.apiUrl,
            {'device': JSON.stringify(this.deviceInfo)},
            [{'name': 'file1', 'file': this.file1}, {'name': 'file2', 'file': this.file2}]
        ).subscribe(
            (response) => console.log(response.json()),
            (response) => console.log(response.json())
        );
    }

}



DeviceResource.java
/**
 * REST controller for managing Device.
 */
@RestController
@RequestMapping("/api")
public class DeviceResource {

    /**
     * POST  /devices : Create a new device.
     *
     * @param device the device to create
     * @return the ResponseEntity with status 201 (Created) and with body the new device, or with status 400 (Bad Request) if the device has already an ID
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PostMapping("/devices")
    @Timed
    public ResponseEntity<Device> createDevice(
  @RequestPart Device device, 
  @RequestParam (value = "file1", required = false) MultipartFile file1, 
  @RequestParam (value = "file2", required = false) MultipartFile file2
 ) throws URISyntaxException {
  
        log.debug("REST request to save Device : {}", device);
        if (device.getId() != null) {
            return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new device cannot already have an ID")).body(null);
        }
        if (file1 != null) {
   // File 저장 로직
        }

        if (file2 != null) {
   // File 저장 로직
        }
  
  // 이후 로직 ..
  
    }
}



PS.
정석대로 한다면 Header에 multipart-mixed로 지정을 하고 일반 파라메터에는 filename 같은게 붙으면 안되지만..
위의 방법으로는 blob를 사용함으로써 쓸데없는 헤더가 붙어 제대로된 방법은 아니나, 작동하는데 크게 문제가 없으며, 이해하기 쉽고 소스가 많이 줄어 그냥 위와같이 하게 되었다.
1004lucifer
아래의 방법을 한번 볼 필요가 있다. (소스가 깔끔하지는 않다..ㅠ)
https://stackoverflow.com/questions/28133289/angularjs-how-to-send-multipart-mixed




참고
- https://ko.wikipedia.org/wiki/MIME
- https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Submitting_forms_and_uploading_files
- http://restlet.com/company/blog/2016/08/29/whats-new-in-the-http-module-of-angular-2/   (나와 같은 방법으로 작업이 되어있다. 깔끔한 설명!!!)



댓글