본문 바로가기

백엔드 개발/SpringMVC

HTTP에서 Binary File을 전송하기 위한 방법 (MIME, Base64)

1. HTTP에서 왜 일반적으론 Binary File을 전송할 수 없는가? 

(1) 파일 종류

구분 Binary 파일 텍스트 파일
저장하는 내용 문자와 숫자 모두 저장 문자만 저장 가능
데이터 읽을 때 적힌 그대로 읽는다 .
문자 -> 문자로 읽음,
숫자 -> 숫자로 읽음
숫자는 문자로 변환 해서 읽는다. 
문자 -> 문자
숫자 -> 문자
데이터 쓸 때  적힌 그대로 쓴다. 
문자 -> 문자 
숫자 -> 숫자 
숫자도 문자로 변환 했기 때문에 
문자만 쓴다. 
문자 -> 문자
예시 이미지, 동영상 파일, 프로그램 글로 적힌 것 

(2)왜 HTTP 에선 Binary file을 못 읽는가? 

HTTP는 텍스트 기반의 프로토콜, 일반적으로 텍스트 파일을 읽기 위해 디자인 되었다. 따라서 

바이너리 파일을 읽으려면 다른 방법이 필요하다.

여기에는 브라우저에게 어떻게 읽어야 하는지 가이드 라인을 주고 바이너리 파일을 그대로 전송하는 MIME 방법과 

바이너리 파일을 일정 규칙을 통해 텍스트 파일로 전부 변환하여 보내는 Base64 방법이 있다.

2. 전송하기 위한 방법 (MIME)

HTTP에서 바이너리 파일(이미지, 동영상)을 읽기 위해 고안된 방법. 

HTTP 메세지 헤더 부분의 Content type에 해당 메세지가 무슨 타입인지 적어준다. 

그리고 body 부분에 바이너리 데이터를 통째로 보내면 브라우저가 헤더의 contentType을 미리 읽고 

어떻게 해석 해야할지 감을 잡고 그에 맞게 해석한다.

(타입/ 서브타입 형식으로 적는다.)

하지만 만약에 이미지 문서를 보내면서 

ContentType : "text/html"로 보내면, 이미지 문서의 binary data를 html 읽듯이 읽어버려서 브라우저에 화면이 전부 깨져서 보일 것이다. 

주 타입 설명 MIME 타입 예시(주 타입/ 서브 타입)
text 텍스트 포함 문서  text/plain -> 진짜 텍스트 문서
text/html -> html 문서
text/css -> Css 문서
imgae 모든 종류의 이미지  image/bmp, image/webp
audio 모든 종류의 audio 파일 audio/midi, audio/mpeg 

 

예시

response.setContentType("text/html"); 
// 해당 http 메세지를 text/html로 해석해라는 뜻

3. 전송하기 위한 방법 (Base64)

(1) 방법

base64는 64진법을 이야기 하는 것이다.

A~Z (26개), a~z(26개), 0~9(10개), + / (2개) 해서 총 64개의 문자로 바이너리 데이터를 텍스트로 변환한다.

64는 2의 6승이다. 따라서 해당 64개의 문자는 6bit 내에서 전부 표현 가능하다. 

**참고** 

ASCII 코드 128문자(2^7), 7bit로 데이터를 전부 변환하는 방법도 있다. 128개에는 개행문자와 이모티콘, 쓸 수 없는 문자도 들어있다. 하지만 개행 문자 같은 것이 OS마다 2진수로 정의된 것이 달라서, 해당 변환은 정확도가 떨어지고 안전하지 않다. 

따라서 여기서 필요 없는 이모티콘과 개행문자를 빼고 만국 공통으로 쓰이는 64개의 문자만으로 데이터를 변환 한 것이 Base 64이다.

(2) 예시 

(3) 장단점

a. 장점

: 만국 공통으로 쓰이는 문자로만 변환 했기에 아랍 OS와 한국 OS 간에도 데이터를 정확하게 변환하여 서로 주고 받을 수 있다. 

--> 데이터 변환이 안전하고 정확하다.

 

b. 단점

위의 예시에서 볼 수 있듯이, 총 16bit 짜리 데이터를 6bit씩 짤라서 char 문자로 변환 했다. char 문자의 크기는 8bit이므로, 

6bit 하나 당 8 bit가 되었다!  

이는 원래 데이터 량의 33%를 뻥튀기 하는 것이다. 

단점: 변환할 때 데이터 량이 늘어난다.

4. 코드 리뷰 

(1)RequestHeader 

/requestHeader로 RequestMapping이 일어나면, 요청 메세지의 헤더를 전부 출력해주는 프로그램.

package com.fastcampus.ch2;

import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RequestHeader {
	@RequestMapping("/requestHeader")
	public void main(HttpServletRequest request) {
		
        //반복자의 구버전, 객체 e는 header의 이름을 순회함.
		Enumeration<String> e = request.getHeaderNames();

		//반복자가 맨 끝에 갈 때까지 반복
		while (e.hasMoreElements()) {
			String name = e.nextElement();
            
            // header name과 해당 header name이 가지는 value를 쌍으로 출력
			System.out.println(name + ":" + request.getHeader(name));
		}
	}
}
package com.fastcampus.ch2;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RequestMessage {
	@RequestMapping("/requestMessage")
	public void main(HttpServletRequest request) throws Exception {
		
		// 1. request line
        //requestLine에 method부터 시작해서 이어붙임. 
        // 옆에 //들은 뭘 이어 붙이는지 보여줌 
		String requestLine = request.getMethod();       // Request 요청이 무엇이었는가? (GET or POST)
		requestLine += " " + request.getRequestURI();   // /ch2/requestMessage (Uri가 쿼리 뺀 부분까지)
		
		String queryString = request.getQueryString();  // year=2021&month=10&day=1
		requestLine += queryString == null ? "" : "?"+queryString;  
		requestLine += " " + request.getProtocol();     // HTTP/1.1
		System.out.println(requestLine);		
        
        // 최종으로 이어 붙인 문장
        //GET /ch2/requestMessage?year=2021&month=10&day=1 HTTP/1.1

		
		// 2. request headers
		Enumeration<String> e = request.getHeaderNames();

		while (e.hasMoreElements()) {
			String name = e.nextElement();
			System.out.println(name + ":" + request.getHeader(name));
		}
		
		// 3. request body - POST일 때만 해당, GET은 body가 없음(CONTENT_LENGTH=0)
        
        //body 길이 
		final int CONTENT_LENGTH = request.getContentLength();
//		System.out.println("content length="+CONTENT_LENGTH);


		// body가 있는 경우		
		if(CONTENT_LENGTH > 0) {
        
        // 바디 길이 만큼 배열을 만듦
			byte[] content = new byte[CONTENT_LENGTH];

		// stream을 만듦
			InputStream in = request.getInputStream();
		
        // content 배열의 0에서부터 끝까지 스트림으로 읽음. 
        	in.read(content, 0, CONTENT_LENGTH);
			
			System.out.println(); // empty line
			System.out.println(new String(content, "utf-8")); // year=2021&month=10&day=1
		}  // if
	} // main
}

5. 스스로 해보기 

이미지 파일을 base64를 이용해 텍스트 파일로 변환 해서 html에 집어넣음. 

이러면 이미지 파일이 안 깨지고 잘 나옴.

'백엔드 개발 > SpringMVC' 카테고리의 다른 글

관심사 분리와 MVC 패턴 (실습)  (0) 2023.03.01
관심사 분리와 MVC 패턴에 대해  (0) 2023.03.01
HTTP 요청과 이론  (0) 2023.02.28
설정 파일 - server.xml 과 web.xml  (0) 2023.02.27
클라이언트와 서버  (0) 2023.02.27