- HTTP 요청과 응답

 

1. 프로토콜(protocol)이란?

서로 간의 통신을 위한 약속, 규칙

주고 받을 데이터에 대한 형식을 정의한 것

 

2. HTTP(Hyper Text Transfer Protocol)?

텍스트를 전송하기 위한 프로토콜

 

특징)

텍스트 기반의 프로토콜 - 단순하고 읽기 쉽다.

상태를 유지하지 않는다(stateless) - 클라이언트 정보를 저장하지 않음

확장 가능하다 커스텀 헤더(header) 추가 가능

 

3. HTTP 메시지

요청 메시지 <-> 응답 메시지

 

 

4. HTTP 메시지 응답 메시지 format

상태코드 / 의미

1xx / Information

2xx / Success 성공

3xx / Redirect 다른 URL 재 요청

4xx / Client Error 클라이언트 에러, 요청이 잘못됨 404 Not Found

5xx / Server Error 서버 에러, 요청은 정상적인데 서버에서 처리 중 에러 발생

 

5. HTTP 메시지 요청 메시지

HTTP 요청 메시지 GET 메소드

단순히 리소스를 얻어오기 위함

요청 라인과 헤더로 구성되어 있고 바디가 없음. 요청 라인에 쿼리스트링을 통해 작은 데이터를 전달할 수 있음.

 

HTTP 요청 메시지 POST 메소드

서버에 정보를 제공해야 할 때 사용

요청 라인과 헤더, 바디로 구성되어 있음. 서버에 전송할 데이터가 바디에 들어가 있음.

바디에 데이터를 담아서 요청할 때 POST를 사용

(글쓰기, 로그인, 회원가입, 파일첨부 등)

* HTTPS = HTTP + TLS (암호화 덕분에 보안에 유리)

 

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

바이너리 파일 : 문자와 숫자가 저장되어 있는 파일. 데이터를 있는 그대로 읽고 쓴다.

텍스트 파일 : 문자만 저장되어 있는 파일. 숫자를 문자로 변환 후 쓴다.

 

 

8. MIME(Multipurpose Internet Mail Extensions)

텍스트 기반 프로토콜에 바이너리 데이터를 전송하기 위해 고안

내가 보내는 데이터가 어떤 타입인지 MIME 타입을 적어줘야 함.

 

9. Base64

->64진법

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

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

-> 이미지 파일을 Base64로 인코딩하여 16진수로 나타냄.

 

 

- 관심사의 분리, MVC 패턴

 

1. 관심사의 분리 Separation of Concers

관심사 = 해야 할 작업

 

코드 분리 point 1. 관심사 2. 변하는 것, 자주 변하지 않는 것 3. 공통 코드(중복)

 

2. 공통 코드의 분리 입력의 분리

입력 부분을 매개변수로 처리함

 

3. 출력(view)의 분리 변하는 것과 변하지 않는 것의 분리

 

 

4. MVC 패턴

MVC 패턴 : Model, View, Controller 의 처리 방식

 

요청을 받으면 DispatcherServlet이 입력처리를 하여 Model(결과를 저장할 객체)을 만들어 Controller에게 전달하고, Controller가 처리를 해서 Model에 담아서 다시 보낸다. DispatcherServlet은 작업 결과가 담긴 ModelView에 전달하고, View에서 작업 결과를 읽어서 응답을 만들어내서 클라이언트에게 전송함.

 

 
ex)
package com.fastcampus.ch2;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

// 년월일을 입력하면 요일을 알려주는 프로그램
@Controller
public class YoilTellerMVC { // http://localhost:80/ch2/getYoilMVC?year=2022&month=4&day=8
		@RequestMapping("/getYoilMVC")
//		public void main(HttpServletRequest request, HttpServletResponse response) throws IOException {
		public String main(int year, int month, int day, Model model) throws IOException {
		
			
			// 1. 유효성 검사
			if(!isValid(year, month, day))
				return "yoilError";
			
			// 2. 요일 계산
			char yoil = getYoil(year, month, day);
		
			// 3. 계산한 결과를 model에 저장
			model.addAttribute("year", year);
			model.addAttribute("month", month);
			model.addAttribute("day", day);
			model.addAttribute("yoil", yoil);
			
		return "yoil"; //	/WEB-INF/views/yoil.jsp
			
	}

	private boolean isValid(int year, int month, int day) {

		return true;
	}

	private char getYoil(int year, int month, int day) {
		Calendar cal = Calendar.getInstance();
		cal.set(year, month - 1, day);
		
		int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
		return " 일월화수목금토".charAt(dayOfWeek);
	}

}

매개변수로 우리가 필요한 int 타입의 year, month, day, 그리고 model을 받게 작성하였다.

실제로 쿼리스트링에서 받는 데이터는 String타입인데도 바로 int 타입으로 받을 수 있는 이유는

DispatcherServlet에서 데이터를 매개변수에 맞게 변환해주기 때문이다.

model 객체 또한 생성해준다.

 

우리는 변환된 데이터를 Controller에서 처리하고 model에 작업결과를 저장할 수 있도록 코드를 작성하면 된다.

 

 

 

 

5. ModelAndView

ModelView가 객체에 함께 저장됨.

 

ex) ModelAndView를 이용

 
package com.fastcampus.ch2;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

// 년월일을 입력하면 요일을 알려주는 프로그램
@Controller
public class Practice { // http://localhost:80/ch2/getYoilMVC?year=2022&month=4&day=8
		@RequestMapping("/getYoilMVC3")
//		public void main(HttpServletRequest request, HttpServletResponse response) throws IOException {
		public ModelAndView main(int year, int month, int day) throws IOException {
			
			// 1. ModelAndView를 생성하고, 기본 뷰를 지정
			ModelAndView mv = new ModelAndView();
			mv.setViewName("yoilError"); // 뷰의 이름을 지정
			
			// 2. 유효성 검사
			if(!isValid(year, month, day))
				return mv;
			
			// 3. 요일 계산
			char yoil = getYoil(year, month, day);
		
			// 4. ModelAndView에 작업한 결과를 저장
			mv.addObject("year", year);
			mv.addObject("month", month);
			mv.addObject("day", day);
			mv.addObject("yoil", yoil);
			
			// 5. 작업 결과를 보여줄 뷰의 이름을 지정
			mv.setViewName("yoil");
			
			// 6. ModelAndView를 반환
			return mv;
			
	}

	private boolean isValid(int year, int month, int day) {

		return true;
	}

	private char getYoil(int year, int month, int day) {
		Calendar cal = Calendar.getInstance();
		cal.set(year, month - 1, day);
		
		int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
		return " 일월화수목금토".charAt(dayOfWeek);
	}

}

ModelAndView는 model과 view가 함께 들어있는 타입이다.

그러므로 ModelAndView 타입의 객체를 생성해서 view의 이름을 지정하고, 작업결과 또한 이 객체에 저장한 후

그렇게 view 이름과 작업결과가 저장된 ModelAndView 객체를 반환하면 된다.

 

 

 

다음은

어떻게 매개변수에 맞게 데이터가 변환되고 모델 객체를 생성하는지, 또 model에 저장된 작업결과를 어떻게 view에서 읽고 처리할 수 있는가를 대략적으로 보여주는 예제를 살펴본다.

package com.fastcampus.ch2;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;

import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;

public class MethodCall3 {
	public static void main(String[] args) throws Exception{
		// 1. 요청할 때 제공된 값 - request.getParameterMap();
		Map map = new HashMap();
		map.put("year", "2021");
		map.put("month", "10");
		map.put("day", "1");

		Model model = null;
		Class clazz = Class.forName("com.fastcampus.ch2.YoilTellerMVC");
		Object obj  = clazz.newInstance();
		
		// YoilTellerMVC.main(int year, int month, int day, Model model)
		Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
				
		Parameter[] paramArr = main.getParameters(); // main메소드의 매개변수 목록을 가져온다.
		Object[] argArr = new Object[main.getParameterCount()]; // 매개변수 갯수와 같은 길이의 Object 배열을 생성
		
		for(int i=0;i<paramArr.length;i++) {
			String paramName = paramArr[i].getName();
			Class  paramType = paramArr[i].getType();
			Object value = map.get(paramName); // map에서 못찾으면 value는 null

			// paramType중에 Model이 있으면, 생성 & 저장 
			if(paramType==Model.class) {
				argArr[i] = model = new BindingAwareModelMap(); 
			} else if(value != null) {  // map에 paramName이 있으면,
				// value와 parameter의 타입을 비교해서, 다르면 변환해서 저장  
				argArr[i] = convertTo(value, paramType);				
			} 
		}
		System.out.println("paramArr="+Arrays.toString(paramArr));
		System.out.println("argArr="+Arrays.toString(argArr));
		
		
		// Controller의 main()을 호출 - YoilTellerMVC.main(int year, int month, int day, Model model)
		String viewName = (String)main.invoke(obj, argArr); 	
		System.out.println("viewName="+viewName);
		
		// Model의 내용을 출력 
		System.out.println("[after] model="+model);
				
		// 텍스트 파일을 이용한 rendering
		render(model, viewName);			
	} // main
	
	private static Object convertTo(Object value, Class type) {
		if(type==null || value==null || type.isInstance(value)) // 타입이 같으면 그대로 반환 
			return value;

		// 타입이 다르면, 변환해서 반환
		if(String.class.isInstance(value) && type==int.class) { // String -> int
			return Integer.valueOf((String)value);
		} else if(String.class.isInstance(value) && type==double.class) { // String -> double
			return Double.valueOf((String)value);
		}
			
		return value;
	}
	
	private static void render(Model model, String viewName) throws IOException {
		String result = "";
		
		// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
		Scanner sc = new Scanner(new File("src/main/webapp/WEB-INF/views/"+viewName+".jsp"), "utf-8");
		
		while(sc.hasNextLine())
			result += sc.nextLine()+ System.lineSeparator();
		
		// 2. model을 map으로 변환 
		Map map = model.asMap();
		
		// 3.key를 하나씩 읽어서 template의 ${key}를 value바꾼다.
		Iterator it = map.keySet().iterator();
		
		while(it.hasNext()) {
			String key = (String)it.next();

			// 4. replace()로 key를 value 치환한다.
			result = result.replace("${"+key+"}", ""+map.get(key));
		}
		
		// 5.렌더링 결과를 출력한다.
		System.out.println(result);
	}
}

위와 같은 과정을 통해 String 타입을 int 타입으로 변환하고 Model 객체를 만들어서

YoilTellerMVC.java 파일의 main 메소드를 통해 Model 객체에 작업결과를 저장했다.

그리고 Model 객체의 작업결과를 jsp 파일의 특정부분에서 replace 메소드를 이용해 치환시켜 출력하였다.

 

 

 

 

6. 컨트롤러 메소드의 반환타입

 

[String] : 뷰 이름을 반환

[void] : 맵핑된 url의 끝단어가 뷰 이름

[ModelAndView] : Model과 뷰 이름을 반환

 

 

 

출처 : 스프링의 정석 : 남궁성과 끝까지 간다

+ Recent posts