새소식

반응형
Back-end/Spring

[Spring] HTTP 요청과 응답(feat. AWS 배포, MIME, Base64, 설정파일)

2023.03.25
  • -
반응형

1. 원격 프로그램의 실행

로컬 프로그램 실행

Main이라는 프로그램이 어떤 컴퓨터에 있을 때 이를 로컬 프로그램이라고 하면 이 프로그램을 실행시키기 위해선 'java Main\n' 을 Command Line에 입력하면 되는데 여기서 java는 java.exe(자바인터프리터)를 의미하고 뒤의 Main 클래스의 main() 메소드를 호출하여 프로그램이 실행된다는 의미를 갖는다.

  • "자바인터프리터로 Main 프로그램 실행해 줘."

 

해당 프로그램을 실제로 커맨드에서 실행해 보면 Hello라는 문구가 콘솔에 출력되고 다음 입력을 기다리는 커서가 깜빡거리는 상태가 될 것이다.

  • 이 자바인터프리터가 객체를 새로 생성하지 않고도 main 메소드를 호출할 수 있는 이유는 main 메서드가 static 메서드이기 때문이다. (static이 아니라면 객체를 반드시 생성해야 사용할 수 있음)

 

원격 프로그램 실행

그러면 원격 프로그램을 어떻게 실행시켜야 할까?

원격의 프로그램을 실행시킨다는 것은 내 컴퓨터가 아라 남의 컴퓨터에 있는 프로그램을 내 컴퓨터를 통해 실행시켜야 한다는 말인데 남의 컴퓨터에 있는 프로그램을 그냥 실행시키는 것은 불가능하다.

 

이때 필요한 것이 '브라우저(browser)'이다. 물론 원격 컴퓨터에도 톰캣과 같은 WAS는 있어야 한다. 이 두 가지가 존재해야 원격 프로그램을 실행할 수 있는 상태가 된다.

 

그래서 브라우저에 서버 컴퓨터의 IP주소(+포트)를 입력을 하고 호출을 하면 이 요청을 톰캣이 받아서 해당하는 프로그램을 실행하게 된다. 그러나 원격 컴퓨터의 다른 모든 프로그램을 아무거나 다 실행시킬 수는 없도록 해야하기 때문에 특정 프로그램만 실행시킬 수 있게 하는 두 가지 작업이 미리 진행되어야 한다.

  • 위 예시에서는 서버 컴퓨터의 IP가 11.22.33.44, 톰캣 port가 8080이라고 가정

두 가지 작업은 다음과 같다.

 

1. 프로그램 등록

이 프로그램을 외부에서 호출할 수 있도록 사전에 등록을 해 주어야 한다.

 

2. URL과 프로그램을 연결

해당하는 프로그램을 실행하려면 어떤 URL로 호출을 해야 하는 지를 연결시켜야 한다.(매핑 과정)

 

코드로 보는 예시

다른 포스팅에서 배우겠지만 @Controller 라는 Annotation을 특정 클래스 앞에 붙여주면 프로그램을 등록한다는 의미이고 호출하고자 하는 메서드에는 @RequestMapping 이라는 Annotation을 붙여 그 안에 사용할 URL을 매개변수로 등록해 주면 특정 URL과 main메서드를 연결하게 되는 것이다.

 

그래서 예를 들어 사용자가 http://111.222.333.444:8080/ch2/hello 라는 URL로 브라우저에서 접속을 하게 되면 main 메서드에 해당하는 프로그램이 실행이 되는 것인데, 위 코드를 자세히보면 전체 URL 주소를 전부 적은 것이 아니라 뒤에 슬래쉬 부분만 적은 것을 확인할 수 있는데 이는 다른 포스팅에서 더 자세히 다루도록 하겠다.

  • 참고로 path에 포함된 'ch2'의 경우 context root(project root)라고 한다.

 

또한 호출 메서드는 Annotation을 통해 호출 여부를 결정하기 때문에 그 메서드 이름은 어떤 것으로 짓든지 상관이 없다. 다시 말해 꼭 main이라는 이름으로 지을 필요가 없으며 해당 메서드의 특징을 잘 표현하는 이름으로 지으면 되는 것이다.

 

 

STS에서 실습

IntelliJ도 좋은 IDEA지만 STS가 무료이기 때문에 사용법을 익히면 언제든 사용할 수 있으므로 STS로 먼저 실습을 진행하도록 하겠다.

 

 

https://download.springsource.com/release/STS/3.9.17.RELEASE/dist/e4.20/spring-tool-suite-3.9.17.RELEASE-e4.20.0-win32-x86_64.zip

위 링크를 통해 sts 압축파일을 다운로드 받고 압축을 풀면 안에 

이와 같은 세 개의 폴더가 존재하는데 sts-3.9.19.RELEASE 폴더로 들어가 나뭇잎 아이콘이 있는 STS.exe 응용 프로그램을 실행시켜주면 sts프로그램이 실행이 된다.

 

그 후 File > New > Spring Legacy Project 로 들어간다.

 

위와 같은 창에서 프로젝트 이름을 입력하고 Templates 부분에서 Spring MVC Project를 클릭하고 Finish를 누른다.

  • 만약에 Spring MVC Project가 없다면 아래 Configure templates를 클릭하여 spring defaults를 제외한 나머지 template을 제거하고 적용하면 뜨게 된다.

 

패키지 이름 입력

패키지 이름까지 입력하고 finish 버튼을 누르면 프로그램 화면 우측 하단에 프로그레스바가 작동하고 몇 초 후에 완료된다.

 

이제 sts에서 Tomcat 서버를 연결해 주어야 하는데 프로그램 좌측 하단에 Servers 라는 탭이 있는데 해당 부분에서 우리가 설치한 톰캣의 위치를 지정하여 새로 tomcat 서버를 추가해 주도록 한다.

Tomcat 서버를 추가한 모습

 

우리가 만든 프로젝트 내부를 보면 src/main/java 에 HomeController.java라는 파일이 자동으로 생성된 것을 볼 수 있는데 일종의 MVC 템플릿을 지정하였기 때문에 자동으로 생성된 코드이다.

  • "localhost:8080/ch2/: 로 접속하면 해당 컨트롤러와 맵핑된 메서드가 실행된다.

package com.fastcampus.ch2;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller // 1. 원격 호출 가능한 프로그램으로 등록
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	/**
	 * Simply selects the home view to render by returning its name.
	 */
	
	// 2. URL과 메서드 연결
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		
		return "home";
	}
	
}

해당 코드에는 앞서 말한 것과 같이

1. 원격 호출 가능한 프로그램으로 등록

2. URL과 메서드 연결

에 대한 어노테이션이 각각 붙여져 있는 것을 확인할 수 있고 프로젝트를 우클릭하여 run on server 옵션을 클릭하게 되면 서버가 실행되어 브라우저가 켜지면서 "localhost:8080/ch2/" URL에 접속이 되는 것을 확인할 수 있다.

 

참고로 Window > Web Browser > Chrome을 지정하면 기본 브라우저 설정을 크롬으로 바꿀 수 있다.

 

 

그러면 Hello라는 클래스를 ch2 패키지에 새로 만들어 우리만의 프로그램을 테스트해 보도록 하자자.

클래스 생성

package com.fastcampus.ch2;

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

// 1. 원격 호출가능한 프로그램으로 등록
@Controller
public class Hello {
	@RequestMapping("/hello")
	public void main() {
		System.out.println("Hello");
	}

}

코드의 의미는 '/hello'라는 경로로 접속하면 콘솔에 Hello라는 문자열을 출력하도록 구현한 것이다.

 

브라우저에서는 404에러

브라우저를 통해 localhost:8080/ch2/hello 로 접속을 하게 되면 404에러가 뜨는데 이는 아직 HTTP에 대한 부분이 구현되어 있지 않기 때문에 jsp 파일을 찾을 수 없다는 오류가 뜬다. (당연히 아직 구현을 안 했기 때문에 이것이 정상이다.)

 

sts 내의 콘솔창을 확인해 보면 서버가 시작되었다는 문구와 함께 Hello 라는 프로그램이 실행되어 "Hello"라는 문자열이 정상적으로 출력된 것을 확인할 수 있다.

 

static이 없는데 어떻게 호출이 되었을까?

위 코드를 보게되면 main 메서드에 static 키워드가 붙어있지 않는데도 객체를 생성하지 않은 상황에서 메서드 호출이 가능한 것을 볼 수 있었다. 이것이 어떻게 가능한 것일까?

 

그 이유는 해당 과정 중간에서 누군가가 객체 생성을 해 주었기 때문인데, 그 누군가가 바로 Tomcat이며 Tomcat 내부를 까보면 실제로 객체를 생성하는 로직이 구현되어 있었기 때문에 가능했던 것이다.

 

왜 인스턴스 메서드로 선언 해야할까?

사실 static을 붙여서 static 메서드로 해도 정상적으로 동작하기 때문에 무방하기는 하나 static 메서드에서는 인스턴스 변수를 사용하지 못하기 때문에 그렇다. 아래 코드를 보면 이해가 갈 것이다.

// 1. 원격 호출가능한 프로그램으로 등록
@Controller
public class Hello {
	int iv = 10; // 인스턴스 변수
	static int cv = 20; // 클래스 변수(static 변수)
	
	@RequestMapping("/hello")
	public void main() { // 인스턴스 메서드 - iv, cv를 둘 다 사용 가능
		System.out.println("Hello");
		System.out.println(cv); // OK
		System.out.println(iv); // OK
	}
	
	public static void main2() {
		System.out.println(cv); // OK
		System.out.println(iv); // 에러
	} 

}

자바 객체지향을 잘 배웠으면 알겠지만 static 메서드는 객체가 생성되기 전에도 호출이 가능한데 해당 메서드가 호출 된 시점에 아직 객체가 생성 되지 않은 경우 해당 메서드 안에서 인스턴스 변수에 접근하게 되면 그 변수는 아직 만들어지기 전이기 때문에 값을 가져올 수 없어 오류가 나게 된다.

 

이러한 이유로 가능하면 인스턴스 메서드로 구현하는 것이 좋다. (물론 문제가 생길 여지가 없이 코드를 짠다면야 static으로 해도 상관은 없다.)

 

만약 메서드 접근 제어자가 private이면?

main 메서드가 public이 아니라 private이어도 메서드 호출이 정상적으로 되는데 이는 @RequestMapping("/hello") 라는 Annotation이 URL이랑 연결이 되게끔 되어있기 때문이다. 해당 코드를 외부에서 호출 가능하도록 구현이 되어있고 따라서 접근제어자 상관없이 해당 메서드가 호출될 수 있다.

 

반대로 같은 패키지 내에 어떠한 클래스를 새로 만들어서 Hello 객체를 생성하여 main 메서드를 호출하려면 이건 당연히 private 메서드이기 때문에 호출이 되지 않을 것다.

public class Main {
    Hello hello = new Hello();
    hello.main(); // private이라서 외부 호출 불가
}

그렇다면 @RequestMapping을 적기만 하면 호출이 되는 이유는 무엇일까?

그 이유는 Relection API를 사용하기 때문인데 이는 클래스 정보를 얻고 다룰 수 있는 강력한 기능을 제공하는 API이다.

 

 

Reflect API를 사용해서 직접 객체를 new로 생성하지 않고 private 메서드를 호출할 수 있는 코드를 구현해 보도록 하겠다.

package com.fastcampus.ch2;

import java.lang.reflect.Method;

public class Main {
	public static void main(String[] args) throws Exception {
		// Reflection API를 사용 - 클래스 정보를 얻고 다룰 수 있는 강력한 기능 제공
		// java.lang.reflect 패키지를 사용
		// Hello 클래스의 Class객체(클래스의 정보를 담고 있는 객체)를 얻어온다.
		Class helloClass = Class.forName("com.fastcapmus.ch2.Hello");
		Hello hello = (Hello)helloClass.newInstance(); // Class 객체가 가진 정보로 객체 생성
		Method main = helloClass.getDeclaredMethod("main");
		main.setAccessible(true); // private인 main()을 호출가능하게 한다.
		
		main.invoke(hello); // hello.main()
	}
}

클래스 파일 (*.class)이 메모리에 올라갈 때, 클래스 파일마다 Class 객체가 하나씩 생성된다.

 

클래스 객체는 클래스의 정보를 모두 다 갖고 있기 때문에 뭐든지 다 알 수 있다. (모든 정보를 가지고 있음)

 

위처럼 newInstance()로 관련 정보를 가져와 그 중에서 해당 클래스에 정의된 main 메서드를 가져와 setAccessible 메서드로 접근 가능 여부를 true로 바꾸고 invoke 메서드로 main 메서드를 호출하게 되면 private으로 되어 있어도 호출이 가능해 지는 식인 것이다.

 

이와 같이 Reflection API를 사용하여 클래스 정보들을 가져올 수 있고 이를 통해 일반적으로 할 수 없는 기능들을 구현할 수가 있게 된다.

 

결론적으로는 Spring Framework가 Java의 Reflection API를 이용해서 객체를 만들기 때문에 메서드 접근제어자를 private으로 지정해도 호출이 가능한 것이이다.

 

2. AWS에 배포하기

위에서는 로컬에 설치된 tomcat에 접근을 했었는데 이제는 AWS에 배포하여 원격서버에 내가 만든 프로그램을 올려 원격 프로그램을 실행시켜보도록 하겠다.

 

먼저 프로젝트를 특정 파일(WAR)로 export 해야 한다.

프로젝트 우클릭 > Export 클릭

 

war 검색 후 WAR file 선택 후 next 릭

 

Destination을 지정하여 마지막에 ch2.war처럼 확장자를 반드시 붙여주어야 함.

war(web aplication archive)파일은 자바 애플리케이션을 하나로 묶을 때 사용하는 파일 형식으로 사실상 zip파일과 유사하다.

 

이제 우리 컴퓨터에 설치된 war file을 서버 컴퓨터에 원격 데스크톱으로 연결하여 서버 컴퓨터에 해당 파일을 복붙해 줄 것이다.

 

서버 컴퓨터에 이전에 깔았던 톰캣 폴더 위치에 webapps 라는 폴더가 있는데 해당 디렉터리는 웹 애플리케이션들이 설치되는 공간이다. 

 

위 그림과 같이 우리 컴퓨터에 export 했던 war 파일을 붙여 넣고 톰캣을 실행하면 알아서 해당 파일은 해당 디렉터리에 자동으로 압축이 풀리게 된다. 

 

톰캣 실행 & 자동으로 압축이 풀린 모습

위처럼 ch2 라는 폴더가 만들어지게 되면 압축이 제대로 풀렸다는 뜻이고 우리가 짰던 코드가 서버 컴퓨터에 애플리케이션 프로그램으로서 깔리게 되었다는 것을 의미한다. (프로그램을 실행할 수 있게 되었다라는 뜻)

 

내 컴퓨터에서 서버 URL로 접속

이제 다시 내 컴퓨터로 돌아와 원격 서버 IP로 접속을 하게 되면 앞서 내 컴퓨터 로컬 프로그램에서 localhost로 접속했던 것과 마찬가지로 아직 HTTP 응답 요청에 대한 부분을 만들지 않았기 때문에 404 error가 뜨지만 

 

톰캣 콘솔창을 보면 정상적으로 println 구문이 실행된 것으로 보아 프로그램은 정상적으로 잘 돌아간다는 것을 확인할 수 있다.

 

여기까지 따라왔으면 우리가 만든 프로그램을 원격 서버에 잘 설치를 한 것이고 외부 컴퓨터에서 원격 서버에 접속하는 것까지 성공한 것이다.

 

 

※ EC2 인스턴스는 프리티어이기 때문에 특정 사용량 이상을 넘어가면 과금이 된다. 그렇기 때문에 사용하지 않을 때는 중지를 시켜서 사용량을 줄일 수 있도록 하되 중지를 시키면 인스턴스는 탄력적 IP를 사용하기 때문에 IP가 바뀌어버려서 원격 데스크톱을 그 때마다 다시 깔아주어야 한다는 점을 유의해야 한다.

 

 

3. HTTP 요청과 응답 - 실습

앞서 프로그램의 실행 결과가 톰캣에 출력이 되는 것을 볼 수 있었는데 이제는 브라우저에 그 결과를 출력하도록 하는 방법을 배워 볼 것이다.

 

그 전에 로컬 프로그램을 위한 코드를 원격 프로그램을 위한 코드로 바꾸는 작업업을 진행해 보도록 하자.

public class YoilTeller {
    public static void main(String[] args) {
        // 1. 입력
        String year = args[0];
        String month = args[1];
        String day = args[2];

        int yyyy = Integer.parseInt(year);
        int mm = Integer.parseInt(month);
        int dd = Integer.parseInt(day);

        // 2. 처리
        Calendar cal = Calendar.getInstance();
        cal.set(yyyy, mm - 1, dd);

        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        char yoil = " 일월화수목금토".charAt(dayOfWeek);   // 일요일:1, 월요일:2, ... 

        // 3. 출력
        System.out.println(year + "년 " + month + "월 " + day + "일은 ");
        System.out.println(yoil + "요일입니다.");
    }
}

위 코드는 소프트웨어 프로그램을 많이 작성해 봤으면 한 번쯤 만들어봤을 법한 "요일을 구하는 프로그램"이다. main 메서드에 작성했기 때문에 이 자바 파일을 java interpreter를 이용해서 커맨드로 실행할 수가 있고 매개변수 args가 사용되기 때문에 커맨드 작성 시 인자값까지 공백으로 구분하여 같이 적어줘서 실행을 해야 한다.

 

 

sts에서 커맨드로 특정 프로그램을 실행을 하고 싶을 땐 프로젝트 루트 디렉터리에서 target이라는 디렉터리를 우클릭 후
Show In > Terminal을 클릭하면 콘솔창이 target 디렉터리 위치에서 열리게 되고 해당 콘솔에서 커맨드에 'cd classes' 를 입력하여 classes 디렉터리 내부까지 이동하면 우리가 구현한 클래스들이 모여있는 곳으로 가게 되어 해당 공간에서 자바 인터프리터로 특정 클래스 프로그램을 실행하는 것이 가능해 진다.

  • 자바 인터프리터 -> javac 명령어 사용

 

커맨드 창에

java com.fastcampus.ch2.YoilTeller 2023 03 14

를 입력하면 해당 년도 월 일에 해당하는 요일을 알려주는 문구가 콘솔창에 뜨게 된다.

 

java interpreter가 뒤에 있는 클래스의 main 메서드를 호출할 때 인자로 념겨주는 값들은 모두 문자열 형태로 args라는 문자열 배열로 받게 되어 코드 내에서 해당 인자를 사용할 수 있게 된다는 사실을 우리는 알고 있다.

 

그러면 이를 서버의 원격 프로그램을 실행시킬 때와 연관지어 생각해 보자.

  • 원격 프로그램을 실행시킬 때는 브라우저로 요청을 하는데 그 인자를 어떻게 넘겨주지?

마찬가지로 원격 프로그램을 브라우저를 통해 URL을 입력해서 요청을 날리면 톰캣은 HttpServletRequest라는 객체를 만들어서 요청한 정보를 해당 객체에 담아서 HttpServletRequest 객체 형태로 main 메서드의 매개변수로 넘겨줄 수 있게 된다.

  • 즉, 터미널에서 java interpreter로 호출할 때와 유사한 방식
    • String[] args vs. HttpServletRequest request

 

따라서 터미널로 실행할 때의 인자값을 우리는 브라우저에서 실행할 거니까 URL에다가 해당 인자를 포함시켜준다고 이해하면 된다.

 

그래서 이를 request 객체로 받아서 parsing 한 후 요청 정보를 프로그램 내에서 요구사항에 맞게 잘 가공하여 그에 맞는 response를 다시 주게 되는 것이다.

 

 

HttpServletRequest의 메서드

HttpServletRequest 객체에는 해당 객체의 몇 가지 정보를 얻을 수 있는 여러 메서드들이 존재한다.

각 메서드들은 표시된 범위 만큼의 정보를 반환해 주기 때문에 URL에서 얻고자하는 정보를 얻을 때 상당히 유용하게 사용할 수 있다.

  • 하지만 외울 필요는 없음(그 때마다 필요한 것을 찾아서 쓰자)

 

여기서 getQueryString()은 앞서 말한 인자값을 경로에 포함시키는 전체부분을 가져오는 메서드로 스프링 개발 시 굉장히 많이 사용하게 될 메서드이다.

URI에서 '?' 가 query string의 시작을 알리고 각 인자들은 '&'로 구분을 하게 된다. 

 

추가적으로 query string에서 key값을 지정하여 받아온 value 값은 무조건 string이기 때문에 위 예시와 같이 숫자로 변환하여야 하는 데이터(날짜)의 경우에는 "int yyyy = Integer.parseInt(year);" 와 같이 변환해서 사용 해야한다.

 

또한 Enumeration(Iterator의 하위호환) 형태로 key값들만 가져올 수도 있고,

Map 형태의 key,value 쌍으로 가져오는 메서드도 존재한다.

 

그리고 동일한 key값에 대해 여러 값들이 들어오는 경우도 존재하는데 이럴 땐 getParameterValues() 메소드로 array 형태로 value들을 가져올 수도 있다.

 

 

 

이 로컬 프로그램을 원격 프로그램으로 변경하기 위해서는 다음과 같은 것들을 수정/변경 하면된다.

1. 클래스에 @Controller 애너테이션 추가

2. main메서드에 @RequestMapping([path]) 추가

3. main 메서드에 붙은 static 제거

4. main 메서드 인자 String[] args -> HttpServletRequest request, HttpServletResponse response 로 변경

5. args[0], ... 과 같이 받던 인자를 request.getParameter("Year")와 같이 받는 것으로 변경

6. 출력을 콘솔 창에서 -> 브라우저로 변경

 

1번부터 5번까지는 우리가 지금까지 했던 것이기 때문에 똑같이 코드를 작성 및 변경을 해 주면 되는데 6번은 어떻게 해야 할까?

 

인자를 받을 때 HttpServletRequest를 사용했다면 결과를 콘솔이 아니라 브라우저로 넘겨주기 위해서는 HttpServletResponse를 사용하면 된다.


HttpServletResponse 객체에도 역시 응답을 어떤 형식으로 할 지에 대한 내용을 담아주고 브라우저로 보내야 하는데 그 내용으로는 많은 것들이 있지만 다음과 같이 html 코드를 사용한다.

response.setContentType("text/html");    // 응답의 형식을 html로 지정
response.setCharacterEncoding("utf-8");  // 응답의 인코딩을 utf-8로 지정
PrintWriter out = response.getWriter();  // 브라우저로의 출력 스트림(out)을 얻는다.
out.println("<html>");
out.println("<head>");
out.println("</head>");
out.println("<body>");
out.println(year + "년 " + month + "월 " + day + "일은 ");
out.println(yoil + "요일입니다.");
out.println("</body>");
out.println("</html>");
out.close();

브라우저는 보여주는 페이지가 보통 html 형식으로 되어있기 때문에 해당 형식에 맞게끔 HttpServletResponse 객체를 통해 설정을 해 준 것이다.

 

브라우저는 우리가 보내는 데이터가 text인지 binary 인지 구분하지 못하기 때문에 setContentType 속성으로 응답의 형식을 알려주고, 응답의 인코딩을 setCharacterEncoding 속성으로 utf-8을 지정해 줌으로써 text를 정형화된 형태로 출력하도록 하였.

 

이를 response 객체의 getWriter() 메서드로부터 PrintWriter라는 객체로 받아 해당 객체의 println()으로 출력을 하면 그 출력 형식 및 내용이 브라우저에 그대로 출력이 되는 것이다.

 

<YoilTeller.java>

package com.fastcampus.ch2;

import java.io.IOException;
...
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class YoilTeller {
    @RequestMapping("/getYoil") // http://localhost:8080/ch2/getYoil?year=2021&month=10&day=1
    //    public static void main(String[] args) {
    public void main(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 1. 입력
//        String year = args[0];
//        String month = args[1];
//        String day = args[2];
        String year = request.getParameter("year");
        String month = request.getParameter("month");
        String day = request.getParameter("day");

        int yyyy = Integer.parseInt(year);
        int mm = Integer.parseInt(month);
        int dd = Integer.parseInt(day);

        // 2. 처리
        Calendar cal = Calendar.getInstance();
        cal.set(yyyy, mm - 1, dd);

        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        char yoil = " 일월화수목금토".charAt(dayOfWeek);   // 일요일:1, 월요일:2, ... 

        // 3. 출력
//        System.out.println(year + "년 " + month + "월 " + day + "일은 ");
//        System.out.println(yoil + "요일입니다.");
        response.setContentType("text/html");    // 응답의 형식을 html로 지정
        response.setCharacterEncoding("utf-8");  // 응답의 인코딩을 utf-8로 지정
        PrintWriter out = response.getWriter();  // 브라우저로의 출력 스트림(out)을 얻는다.
        out.println("<html>");
        out.println("<head>");
        out.println("</head>");
        out.println("<body>");
        out.println(year + "년 " + month + "월 " + day + "일은 ");
        out.println(yoil + "요일입니다.");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}

이로써 로컬 프로그램을 위한 코드에서 원격 프로그램(서버)을 위한 코드로 수정이 완료되었다.

 

그렇다면 이제 서버를 구동시키고 접속을 해 보도록 하겠다.

출력 결과

 

 

4. HTTP 요청과 응답 - 예제

이제 다른 예시(주사위)를 통해 연습을 해 보도록 하겠다.

dice.zip
0.08MB

주사위와 관련된 이미지를 우선 다운을 받고 디렉터리 내 src > main > webapp > resources 폴더에 해당 폴더를 넣어줍니다.

  • 이미지 파일은 정적리소스이기 때문에 정적 리소스 저장소를 담당하는 resources 디렉터리에 저장

 

그리고 twoDice 라는 클래스 파일을 하나 만들어 코드를 작성해 보도록 하겠습니다.

 

  • 막간을 이용한 꿀팁 (eclipse IDE)
    • ctrl + shift + o 는 자동 import 

 

<TwoDice.java>

package com.fastcampus.ch2;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

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

@Controller // ctrl + shift + o 는 자동 import 
public class twoDice {
	@RequestMapping("/rollDice")
	public void main(HttpServletResponse response) throws IOException{
		response.setContentType("text/html");
		response.setCharacterEncoding("utf-8");
		PrintWriter out = response.getWriter();
		out.println("<html>");
		out.println("<head>");
		out.println("</head>");
		out.println("<body>");
		out.println("<img src='/img/dice1.jpg'>");
		out.println("<img src='/img/dice2.jpg'>");
		out.println("</body>");
		out.println("</html>");
	}
}

위 코드는 html 형식으로 두 개의 주사위 이미지 파일을 띄우는 굉장히 단순한 코드이다.

 

하지만 브라우저에서 이를 확인해 보면 엑박(엑스박스)이 보이이게 된다.

 

그 이유를 알아보기 위해 페이지를 우클릭 하여 페이지 소스 보기를 클릭하면 아래와 같이 페이지 소스 코드를 볼 수 있는데, 정상적으로 html 소스 코드가 전달되어 잘 띄우는 것을 확인할 수 있다. 그래서 다시 한 번 페이지를 우클릭 하여 검사 버튼을 클릭하면

 

이처럼 리소스를 로드할 수 없다는 오류가 발생한 것을 확인할 수 있었다.

 

그래서 직접 내가 지정했던 이미지 소스 경로를 path에 지정하여 접속해 본 결과 실제로 찾을 수 없다고 404 에러가 뜨고 있었다.

 

알고봤더니 img 앞에 resources라는 경로가 빠져서 그런 것이었고 코드 상에서 이를 수정하여 다시 서버를 구동해 본다.

out.println("<img src='/resources/img/dice1.jpg'>");
out.println("<img src='/resources/img/dice2.jpg'>");

 

그러면 위와 같이 정상적으로 두 개의 주사위 이미지가 나오게 된 것을 확인할 수 있다.

 

그렇다면 reload(새로고침)를 누를 때마다 다른 주사위가 나오게 하고 싶다면 어떻게 해야 할까?

 

자바 기본 모듈 중 random 함수를 사용하여 이미지 소스 경로를 그때 그때마다 바꿔줄 수 있도록 하면 될 것이다.

  • 이미지가 6개가 준비되어 있으므로 1~6 범위에서 랜덤한 값이 나오도록 구현

 

int idx1 = (int)(Math.random()*6)+1;
int idx2 = (int)(Math.random()*6)+1;

이와 같이 두 개의 이미지 소스 경로에 들어갈 인덱스 변수를 랜덤으로 뽑는다. 이미지는 1~6까지 있기 때문에 0부터 뽑는 random() 함수의 값에 1을 더해주어야 한다.

 

out.println("<img src='resources/img/dice"+idx1+".jpg'>");
out.println("<img src='resources/img/dice"+idx2+".jpg'>");

그러고 나선 println 부분 img 태그에 src 속성에서 경로를 위와 같이 인덱스를 중간에 넣어주는 것으로 바꿔주면 프로그램을 실행할 때마다 idx값이 바뀔 것이고 다른 이미지가 나오게 될 것이다.

 

새로고침 할 때마다 다른 주사위가 나오는 모습

여기서 우리가 주목해야 하는 사실은 우리가 방금 만든 주사위 프로그램 (TwoDice)은 실행이 될 때마다 결과가 변한다는 사실이다. 그래서 이러한 것은 동적리소스라고 한다.

 

반면 우리가 resources 폴더에 저장한 이미지들은 정적리소스라고한다.(우리가 요청할 때마다 바뀌는 것이 X)

 

서버가 제공하는 리소스에는 동적리소스와 정적리소스가 존재하는데 정적리소스는 앞서 말했듯 이미지 파일이나 *.js 파일, *.css과 같은 파일이고 동적리소스는 프로그램이 생성해 내는 리소스 결과, 스트리밍(라이브 방송)과 같은 것들이다.

 

 

정리하자면 클라이언트가 서버로 요청을 보내게 되면 서버는 프로그램을 실행하고 서버가 가진 리소스를 응답하는 것인데 이 리소스는 보통 html 파일 형식으로 되어있다.

 

따라서 일종의 문자열로 이루어진 텍스트 문서를 만들어 내는 것이다. 그래서 이 문서를 브라우저에게 보내면 브라우저는 그 형식을 해석하여 화면에 띄우게 되는 것이다.

 

5. 클라이언트와 서버

역할에 따른 구분

클라이언트(client): 서비스를 요청하는 애플리케이션(or 컴퓨터)

서버(server): 서비스(service)를 제공하는 애플리케이션(or 컴퓨터)

일반적으로는 단순하게 클라이언트, 서버라고 부르지만 더 세부적으로 나눌 수도 있다.

 

  • 브라우저: 클라이언트 애플리케이션 (Client Application)
  • (사용자) 컴퓨터: 클라이언트 컴퓨터 (Client Computer)
  • (서버) 컴퓨터: 서버 컴퓨터 (Server Computer)
  • 톰캣: 서버 애플리케이션 (Server Application)

 

서버의 종류

서버는 굉장히 다양한 목적을 갖고 있기 때문에 어떤 서비스를 하느냐에 따라 서버의 종류가 나뉜다.

위 사진에서 볼 수 있듯 Email Server에서는 이메일을 제공할 것이고 File Server에서는 파일을 제공할 것이다.

 

그렇다면 Web server(웹서버)에서는 "웹"을 제공할 것이라는 건데 웹을 제공한다는 건 무슨 의미일까?

 

웹이란 단순히 말해 브라우저로부터 받을 수 있는 모든 서비스를 받는다고 생각하면 된다. (문서, 이미지, 동영상...)

 

서버의 포트

만약 한 대의 PC에 서버 프로그램이 여러 개가 있다면 (당연히 보통 여러 프로그램이 존재한다.) 클라이언트 컴퓨터에서 서버 IP에 요청을 날렸을 때 여러 프로그램 중 어떤 프로그램을 실행시켜야 할지, 또 어디로 요청 인자를 넘길지 모르기 때문에 이를 식별할 수 있는 것이 필요하다..

 

이때 필요한 것이 바로 포트 번호이다.

 보통 웹서버의 경우 위 사진처럼 포트번호가 80으로 기본 세팅 되어있기 때문에 생략하면 웹서버로 연결이 자동으로 된다. 하지만 우리가 앞서 만들었던 톰캣 서버의 경우 설정을 통해 8080으로 지정이 되어있었기 때문에 localhost:8080 과 같이 무조건 포트번호를 명시해야 접속이 되었던 것이다.

 

물론 이렇게 작동하기 위해선 특정 기능을 하는 서버와 포트 번호가 바인딩(binding) 되어 있어야 한다. 보통 한 포트 번호에는 한 서버만 연결될 수 있다.

 

어떤 서버가 특정 포트번호와 연결되어 어떠한 요청이 들어오기를 기다리는데 이를 '리스닝(Listening)한다'라고 하고 서버가 먼저 실행되고 있어야 클라이언트의 요청을 받을 수 있다는 사실은 쉽게 이해 할 수 있을 것이다.

 

일반적으로 0~1023 에 해당 하는 포트 번호는 미리 예약되어 있기 때문에 사용할 수가 없고 (웹서버, 파일서버, 이메일 서버...) 1024~65535 까지의 포트 번호를 우리가 사용할 수 있는 포트번호로 제공된다.

 

웹 애플리케이션 서버(Web Application Server)란?

앞서 Web server란 Web을 서비스하는 서버라고 했다. 그렇다면 웹 애플리케이션 서버, WAS는 Web application을 서비스한다는 뜻이 된다.

 

애플리케이션이라는 이름에서 알 수 있듯이 이는 프로그램을 서비스한다는 것이다.

 

서버에 프로그램을 설치해 놓고 클라이언트가 이 프로그램을 사용할 수 있게 되는 것을 의미한다. 로컬에 프로그램을 설치할 수도 있지만 외부에 존재하는 프로그램을 클라이언트가 서비스 받기 위해 구축된 서버라고 보면 된다.

 

이쯤에서 이제 왜 Web이 부흥했는지 그 이유를 알 수 있다.

 

과거, web이 뜨기 전에는 모든 프로그램을 무조건 내 컴퓨터에 설치해야 했는데 그렇게 되면 그 프로그램을 update해야 하는 경우 그 프로그램을 깔고 있는 모든 컴퓨터들이 업데이트를 해야하기 때문에 문제가 되었다. 지금이야 속도가 빨라서 괜찮다고 하지만 옛날에는 업데이트가 굉장히 큰 일이었기 때문이다.

 

그런데 서버에 프로그램을 설치하고 수많은 클라이언트들이 그 서버의 프로그램을 실행할 수 있도록 된 순간, 업데이트를 해야하는 경우 서버 컴퓨터 한 대만 업데이터 하면 그 프로그램을 실행하려 하는 모든 클라이언트는 업데이트를 하지 않아도 요청만 날리고 결과만 받을 수 있기 때문에 굉장히 효율적으로 바뀐 것이다.

 

물론 프로그램을 내가 직접 깔지 않아야 한다는 이유는 메모리 공간도 절약시켜 준다.

  • 프로그램에 악의적인 코드가 심어진 경우도 대비 가능함.

 

Tomcat의 내부 구조

클라이언트가 브라우저로 서버에게 요청을 날리 8080포트에 해당하는 톰캣 서버로 요청이 날라간다.

 

그러면 그 톰캣 서버에는 요청을 받아서 처리할 여러 개의 Thread들이 기다리고 있는데 그 Thread들은 Thread Pool 형태안에 존재한다.

더보기

Thread Pool이란?

여러 스레드를 미리 만들어 놨다가 클라이언트로부터 요청이 오면 한가한 스레드 하나가 그 요청을 맡아 처리하도록 하는데 미리 만들어진 스레드 여러 개 thread pool이라고 한다.

이렇게 스레드를 미리 만들어 놓는 이유는 요청이 왔을 때 스레드를 만들면 응답 속도가 늦어지기 때문이다.

 

 

그리고 이 톰캣 서버 안에는 Service가 존재하는데 그 안에는 Connector라는 것이 존재한다.

 

클라이언트가 요청을 할 때 어떤 프로토콜로 요청하느냐에 따라서 처리할 Connector가 달라지게 된다.

  • 예를 들어 http://~~로 HTTP1.1 요청을 보내면 HTTP1.1 Connector가 맡아 처리를 하는 것이다.

 

그러면 Connector가 요청을 그 안에 있는 엔진(Engine)에게 보내게 되는데 그 엔진의 이름은 'Catalina' 이다.

 

그 엔진 안에는 Host라는 것이 존재하는데 보통은 하나의 호스트가 있지만 여러 개의 호스트가 있는 경우도 종종 있다.

호스트는 우리가 일반적으로 알고 있는 어떠한 경로가 붙지 않은 도메인(사이트 주소)이다.

 

또한, 하나의 호스트 안에는 Context가 존재하며, 이 역시 여러 개가 존재할 수 있다.

이때, 이 하나의 Context를 Web application(Web App)이라고 한다.

 

따라서 하나의 호스트에 여러 개의 웹 애플리케이션이 설치될 수 있는 것이다.

 

Context는 우리가 앞서 만든 하나의 STS 프로젝트라고 볼 수 있으며 앞서 만든 프로젝트를 예시로 들면 /ch2 가 되는 것이다. 

  • 그래서 /ch2를 context root라고 하는 

 

또한 각 Context 안에는 Servlet이라는 것이 존재한다.

 

Servlet에서 let은 '작다'라는 뜻으로 서버를 의미하는 접두어 'ser'와 합쳐져 쉽게 말하자면, 작은 서버 프로그램이 되는 것이다. 이를 또 다른 말로 컨트롤러(@Controller)라고 하기도 한다.

 

서블릿 각각은 해당 서블릿을 실행하기 위해 mapping 되어 있는 경로(path)가 존재한다.(@RequestMapping)

 

정리하면 http://www.naver.com/event/list 라는 url이 있을 때 "http://www.naver.com"에 해당하는 부분은 Host의 이름을 의미하고 /event는 Context, /list는 Servlet의 이름을 의미한다고 할 수 있다.

 

 

6. 설정 파일 - server.xml, web.xml

우리가 설치한 톰캣의 폴더를 보면 conf 폴더가 존재하는데 해당 폴더에 존재하는 설정 파일들을 sts나 intellij에서 복사하여 사용하는 것이다. 이를 통해 하나의 Tomcat 프로그램을 공유하면서 설정만 다른 여러 서버를 등록 가능하게 된다.

 

Server(Tomcat) 밑에 Service(Catalina) 모습

Service 밑에는 Connector가 있고 port에 대한 설정을 보면 8080으로 지정돼어 있는 것을 볼 수 있다. 해당 port 번호를 80으로 바꾸어 주면 앞으로 서버를 실행시키고 브라우저로 URL 접속을 할 때 포트 번호를 붙이지 않아도 된다.

 

이전까지는 이 부분이 8080으로 되어있었기 때문에 무조건 8080으로 포트를 붙여주어야 했지만 브라우저의 웹 기본 포트는 80이기 때문에 톰캣 설정을 80으로 바꾼다면 브라우저에서 이 톰캣서버에 접근할 때는 포트 번호를 붙여주지 않아도 자동으로 80으로 붙여져서 접속이 되기 때문에 포트번호 없이 접근이 되는 것이다.

 

Service 안의 Engine

Engine은 default host가 localhost로 되어있다. 

여기서는 여러 Host를 포함 가능하며, 그 중에서 어떤 Host를 default로 할 것인지 지정할 수 있다.

다만 여기서는 localhost를 default로 지정한 것이다.

 

Engine 밑에 Host

host의 이름은 localhost로 되어있다.

 

unpackWARs 설정이 true라는 것은 WAR로 배포 시 자동으로 압축을 풀라는 의미이다.

appBase가 "webapps"라는 것은 해당 폴더 아래에 WAR 파일을 넣어주게 된다는 것이다.

 

Host 밑에 Context

context는 우리가 지정한 프로젝트 이름인 ch2가 된다.

 

web.xml에는 servlet을 등록하는 부분이 있는데 앞서 봤듯이 원격 프로그램을 실행하기 위해선

1. 서블릿 등록

2. URL 연결

이 두 가지 과정이 진행되어야 하기 때문에 이 과정을 Web.xml에서 모두 설정을 하기도 하지만 이 과정이 번거로워 현재는 Spring의 Annotation을 통해 @Controller, @RequestMapping만 붙이면 되도록 만들어졌기 때문에 그 과정이 굉장히 간소화된 것이다.

 

그래서 web.xml에서 Annotation으로 넘어가는 추세이고 과거 web.xml이 필수였으나 현재는 선택이다. Annotation과 xml 설정을 둘 다 선택하면 충돌이 일어나기 때문에 반드시 둘 중 한 가지 방법만 사용해야 한다.

 

사실 @Controller, @ReqeustMapping은 스프링에서만 사용하고, 서블릿에서는 @WebServlet을 사용한다.(추후 설명)

 

개별 프로젝트 web.xml

그래서 원래는 프로그램 하나 만들 때마다 위처럼 web.xml을 설정해 주어야 하는데 그렇게 되면 해당 파일을 자꾸 여러 사람이 건들여야 하고 작성해야 하는 코드가 길어 귀찮기 때문에 Annotation 기반으로 바뀌게 된 것이다.

 

7. HTTP 요청과 응답 - 이론

프로토콜(protocol)이란?

서로 간의 통신을 위한 약속 규칙을 말하며 꼭 컴퓨터 쪽에서만 사용하는 용어는 아니다.

주고 받을 데이터에 대한 형식을 정의한 것이며, 만약 이 형식을 정해두지 않으면 데이터가 어디서부터 어디까지 어떤 내용인지 해석하기가 어렵다.

 

야구로 예시를 들면 선수와 선수 혹은 선수와 감독끼리 사인을 주고 받는 것과 유사하다고 할 수 있다. 그들만의 사인을 정해 시합 도중 특정 행동을 했을 때 그 사인을 받은 사람은 어떤 행동을 해야 할 지를 알 수 있게 된다.

 

 

 

HTTP(Hyper Text Transfer Protocol)란?

HTTP는 말 그대로 텍스트를 전송하기 위한 프로토콜이다.

  • Hyper Text -> HTML

단순하고 읽기 쉬운 형태의 텍스트를 보내기 때문에 굉장히 유용한 프로토콜 중 하나이다.

 

요청과 응답이 모두 HTTP 프로토콜을 지켜서 데이터를 전송하게 된다. 이는 모두 사람이 읽을 수 있기 때문에 이해하기가 쉽다는 장점이 있다.

 

또한 HTTP는 상태를 유지하지 않는다는 특성(stateless)을 가진다.

그래서 같은 클라이언트가 요청을 두 번 보내더라도 이 두 요청이 같은 사용자가 보낸 것인지 인지하지 못한다. 그래서 이를 보완하기 위해 쿠키와 세션이라는 추가적인 도구를 사용하긴 한다. (이후 포스팅에서 자세히 다룸)

 

마지막으로 확장가능하다는 장점을 갖는데 이는 커스텀 헤더(header)를 추가하는 것이 가능하다는 의미이다.

표준 헤더가 존재하고 대소문자 구분을 하지 않는다.

  • Content-type == content-Type

 

HTTP 메시지

편지와 사실상 그 구조가 굉장히 유사하기 때문에 HTTP 메세지라고도 한다.

 

서로에게 요청 편지를 보낼 수 있고 요청을 받은 상대방은 요청에 대한 응답 편지를 보내는 것으로 생각할 수 있는 것이다..

 

우리는 URL만 입력할 뿐이지만 실제로 브라우저에서는 자동으로 요청 메시지를 형식을 갖춰서 만들고고 보내준다.

서버는 그 정형화된 요청을 받아서 응답에 맞는 메세지를 형식을 맞춰 작성 후 다시 보낸다.

 

위에서부터 상태라인, 헤더, 바디 블럭

응답 메세지는 크게 세 부분, 상태 라인(status line), 헤더, 바디로 구분된다.

 

그러나 헤더는 몇 줄일지 그것을 적기전까지는 아무도 모르기 때문에(가변적) 헤더와 바디를 구분하기 위해 빈 줄을 한 줄 넣어 다.

 

상태코드 별 의미

1xx: 잘 쓰이진 않음(1.1에서 추가 됨) - 정보교환 목적

2xx: 성공적

3xx: 다른 URL로 요청

4xx: 클라이언트가 요청을 잘못함.

5xx: 서버가 처리 중 에러

 

요청 메시지

  • 첫줄: 요청 라인
  • 헤더: N개의 가변 줄
  • 빈줄
  • 바디

GET 메서드에서는 데이터를 URL에 실어 보내기 때문에 바디가 존재한다.

 

GET, POST 비교

  • 크롬의 경우 URL에 들어갈 수 있는 양은 7000글자 정도로 제한되어 있다.
  • HTTP+TLS -> HTTPS
    • TLS없이 보내면 보안에 그닥 유리하지 않음.

 

 

브라우저에서 페이지를 우클릭하여 검사를 클릭한 후 네트워크 탭을 살펴보면 해당 브라우저가 작성한 응답 헤더와 서버로부터 온 요청헤더를 볼 수 있다.

요청과 응답의 공통 헤더들이 나온다.

 

 

 

POST 요청

GET 요청은 URL에 그냥 주소를 보내고 싶은 데이터와 함께 전송하면 되는데 POST 요청은 <form>이라는 태그를 사용해야 하기 때문에 다소 귀찮은 부분이 있어 보통 postman이라는 툴을 사용하여 POST 요청을 만들어서 보낸다.

웹스토어에서 postman 확장프로그램 추가

 

 

 

GET 요청을 만든 모습

이와 같이 인자를 추가하면 자동으로 쿼리스트링이 path에 붙어서 요청이 날라가게 된다.

 

이 도구를 사용하여 POST 요청을 보내보면

첫줄에 요청라인, 그 다음줄부터 요청 헤더, 빈줄, 바디 값이 보내지는 것을 텍스트를 통해 미리 확인할 수 있다.

 

 

8. 텍스트와 바이너리, MIME, Base64

텍스트 파일 vs. 바이너리 파일

바이너리 파일: 문자숫자가 저장되어 있는 파일

텍스트 파일: 문자만 있는 저장되어 있는 파일

이미지 파일을 텍스트 형태로 연 모습

데이터는 크게 문자와 숫자로 구성된다.

이미지 파일을 메모장으로 열게 되면 위와 같이 나오는데 메모장은 text editor이기 때문에 텍스트가 아닌 바이너리 데이터들은 깨지게 되는 것이다.

 

따라서 메모장으로 열었을 때 정상적으로 작성되어 있으면 텍스트 파일, 그렇지 않으면 바이너리 파일이라고 할 수 있다.

 

바이너리 파일 : 데이터를 있는 그대로 읽고 쓴다.

텍스트 파일     : 숫자를 문자로 변환 후 쓴다.

 

텍스트 파일의 경우는 12라는 숫자를 문자로 바꾸어야 하기 때문에 '1', '2' 와 같은 문자로 바꾸게 된다.

  • 4 byte -> 2byte로 줄어드는 효과
    • float의 경우 4byte에서 6byte로 오히려 늘어남

 

그리하여 데이터를 모두 문자로 바꾸게 되면 해당 파일은 텍스트 파일이 되는 것이다. 그래서 텍스트 파일은 사람이 읽기 쉽지만 바이너리 파일은 사람이 읽기 어렵다.

 

 

MIME (Multipurpose Internet Mail Extensions)

MIME은 텍스트 기반 프로토콜에 바이너리 데이터를 어떻게 하면 전송할 수 있을까를 고민하다가 나오게 된 개념이다.

HTTP의 Context-Type 헤더에 사용하며, 데이터 타입을 명시할 때 사용한다.

정리하자면 HTTP 프로토콜은 텍스트 기반의 프로토콜인데 어떻게 바이너리 형태의 이미지나 동영상을 전송할 수 있는 것일까?라는 의문이 있었고 전송할 데이터의 타입을 명시함으로써 이를 해결할 수 있었는데 이것이 MIME이다.

 

사실 앞서 나왔던 response.setContentType("text/html")과 같은 행위가 이를 처리해 주는 것이었다.

내가 보내는 데이터가 바이너리인지 텍스트인지 명시함으로써 정상적으로 데이터를 처리할 수 있게 해 준다.

 

그럼 이제 postman에서 파일을 보내보면서 이해를 해 보도록 하겠다.

POST 요청

---WebKitFormBoundary~~~ 라고 되어있는 것은 바이너리 데이터와 텍스트가 요청에 같이 실려서 가기 때문에 둘 간의 구분을 해 주기 위한 구분자 역할로 들어가는 것이다.

  • 위에서 볼 수 있듯이 이미지와 관련된 데이터를 모아놓은 곳이 바이너리 데이터 정보가 들어가는 곳이다.

 

하지만 우리가 사용하는 postman이도 몇 가지 한계가 존재한다.

경고 문구

크롬에서 보내지는 것을 포함한 모든 헤더를 다 미리 볼 수는 없다는 의미이다.

 

그래서 모든 헤더를 다 보기 위해서 새로운 파일을 만들어 코드를 살펴보자.

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) {
		
		Enumeration<String> e = request.getHeaderNames();

		while (e.hasMoreElements()) {
			String name = e.nextElement();
			System.out.println(name + ":" + request.getHeader(name));
		}
	}
}

HttpServletRequest 객체의 getHeaderNames 메서드를 사용하면 모든 헤더를 불러올 수 있게 된다.

 

모든 헤더에 대한 정보를 출력한 모습

 

Base64

2진법 - base2 : 0, 1로 구성된 정보

10진법 - base10: 0~9로 구성된 정보

...

64진법 - base64: 0~63로 구성된 정보

  • A~Z: 26개
  • a~z: 26개
  • 0~9: 10개
  • == 62개
  • '+' , '/' - 2개
  • 그래서 총 64개를 사용하는 진법

 

바이너리 데이터를 텍스트 데이터로 변환할 때 사용

64진법은 '0'~'9', 'A'~'Z', 'a'~'z', '+', '/' 모두 64개 (6 bit)의 문자로 구성

출처:&nbsp;https://en.wikipedia.org/wiki/Base64

 

ASCII 문자는 128개로 이루어져 있고 2^7이기 때문에 7bit이다.

 

64진법은 모든 인코딩 공통이기 때문에 이 6bit를 가지고 변환을 하면 서로 다른 OS를 가진 PC끼리도 해석이 가능하다.

 

6bit씩 끊은 것을 문자 하나로 바꾸니까 8bit씩 끊어야 하는데 이렇게 되면 padding-0가 생겨나 데이터가 늘어나게 되는 것이다.(단점)

하지만 안전하게 데이터를 주고받을 수 있다는 점에서 유용하게 사용할 수 있는 기법이다.

 

그래서 최종적으로 바이너리 데이터를 텍스트 기반인 HTTP 프로토콜로 보내려면 방법이 두 가지가 존재하는 것이다.

1. MIME 타입 쓰고 바이너리를 그냥 그대로 넣는 방법

2. base64를 써서 바이너리를 텍스트 데이터로 변환하여 보내는 방법

 

 

0과 1로 이루어진 이미지 파일은 바이너리 데이터로 이루어져 있을 것이기 때문에 이 데이터를 base64로 인코딩하면 위 그림처럼 된다. 또 다시 이를 디코딩 하면 바이너리 데이터가 되어 이미지가 되는 것이다.

 

실습

https://www.base64encode.net/

 

Base64 Encode - Online Tool

Encode and Decode Base64 data with this online base64 encoder.

www.base64encode.net

위 사이트트에서 base64 변환을 할 수 있는 기능이 제공된다.

 

  • image to base64 클릭 --> 이미지 업로드 --> 텍스트 파일 복사 후 vscode에서 base64test.html 파일에 html 형식을 만들어 body에 img 태그의 src 속성에 붙여넣기 -> open in default browser -> 이미지 나옴 확인 -> 소스보기(바이너리 파일이 텍스트파일로 들어간 것을 볼 수 있음)

 

이미지 태그에 외부링크를 불러온 것처럼 볼 수 있으나 사실은 이미지를 base64 인코딩해서 이미지를 html에 텍스트로 박아버린 것이기 때문에 이 파일의 사이즈는 조금 커지겠지만 html하고 연결된 이미지에 대한 링크가 깨지는 걱정을 하지 않아도 돼서 편리하다.

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.