[1/27] TIL - Client에서 Server로 Data를 전달하는 세가지 방법, Server(Spring)에서 HTTP 응답을 Client에 전달하는 세가지 방법

2025. 1. 27. 21:52카테고리 없음

[RequestParam]

URL에서 파라미터 값과 이름을 함께 전달하는 방식으로 주로 HTTP 통신 Method 중 GET 방식의 통신을 할 때 많이 사용한다. @Requestparam을 사용하면 요청 파라미터 값에 아주 쉽고 간편하게 접근(Parameter Binding)할 수 있다.

?뒤에 오는 URL을 Query String, Query Parameter, Request Param이라 한다.

 

@Slf4j
@Controller
public class RequestParamControllerV2 {

	@ResponseBody
	@GetMapping("/v1/request-param")
	public String requestParamV1 (
					@RequestParam("name") String userName,
					@RequestParam("age") int userAge													
	) {
		// logic
		log.info("name={}", userName);
    log.info("age={}", userAge);
		return "success";
	}

}
  1. @Controller + @ResponseBody
    • View를 찾는 것이 아니라 ResponseBody에 응답을 작성한다(=@RestController)
  2. @RequestParam
    • 파라미터 이름으로 바인딩한다.
  3. @RequestParam(”속성값”)
    • 속성값이 파라미터 이름으로 매핑된다.

“속성값”과 변수명이 같으면 생략이 가능하다.

ex) @RequestParam("name") String name

// GET http://localhost:8080/v2/request-param?name=sparta&age=100
@ResponseBody
@GetMapping("/v2/request-param")
public String requestParamV2 (
				@RequestParam String name,
				@RequestParam int age													
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}

 

required 속성 설정

  • 파라미터의 필수 값을 설정한다.
  • API 스펙을 규정할 때 사용한다.
@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
				@RequestParam(required = true) String name, // 필수
				@RequestParam(required = false) int age	// 필수가 아님										
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}

@RequestParam을 사용하면 기본 Default값은 True이다.

  • True로 설정된 파라미터 값이 요청에 존재하지 않으면 400 BadRequest(클라이언트 측 에러)

주의! http://localhost:8080/v4/request-param?name=sparta 요청한다면?

  • 500 Error가 발생한다.
  • int Type에는 null을 넣을 수 없다. 0이라도 들어가야 한다.
  • 따라서 보통 null을 허용하는 Integer로 사용하거나 default 옵션을 사용한다.
@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
				@RequestParam(required = true) String name, // 필수
			  @RequestParam(required = false) Integer age										
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}

 

파라미터 Key값만 있고 Value가 없는 경우 http://localhost:8080/request-param?name=

  • ull과 빈 문자열 “”은 다르다!
  • 위 형태는 빈 문자열 “” 로 인식한다, True지만 통과가 되어버린다. 주의해야 한다.

 

default 속성 적용

  • 파라미터의 기본 값을 설정한다.
@ResponseBody
@GetMapping("/v5/request-param")
public String requestParam (
				@RequestParam(required = true, defaultValue = "sparta") String name,
		    @RequestParam(required = false, defaultValue = "1") int age											
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success"	
}

주의! defaultValue 속성을 설정하게 되면 “” 빈 문자열의 경우에도 기본 값이 설정된다.

 

Map 사용

  • Parameter를 Map형태로 조회가 가능하다.
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
        @RequestParam Map<String, String> map
) {
    // logic
    log.info("name={}", map.get("name"));
    log.info("age={}", map.get("age"));

    return "success";
}

 

MultiValueMap 형태(key=[value1, value2])로 조회가 가능하다.

@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
        @RequestParam MultiValueMap<String, String> map
) {
    // logic
    log.info("name={}", map.get("name"));
    log.info("age={}", map.get("age"));

    return "success";
}

 

파라미터 Map의 Value가 1개인 경우에는 Map, 여러 개인 경우 MultiValueMap을 사용한다. 하지만 대부분의 파라미터 값은 한 개만 존재한다.

 

[ModelAttribute]

요청 파라미터를 받아 필요한 Object로 바인딩 해준다. 주로 HTML 폼에서 전송된 데이터를 바인딩하고 HTTP Method POST인 경우 사용된다.

@Data
public class Tutor {

	private String name;
	private int age;

}

@Controller
public class ModelAttributeController {

	@ResponseBody
  @PostMapping("/v1/tutor")
  public String requestParamV1(
          @RequestParam String name,
          @RequestParam int age
  ) {
      Tutor tutor = new Tutor();
      tutor.setName(name);
      tutor.setAge(age);

      return "tutor name = " + name + " age = " + age;
  }

}

 

@RequestParam 의 Mapping을 사용하게 되면 위와 같은 객체를 생성하는 코드가 계속 포함되어야한다.

  • @ModelAttribute 는 해당 과정을 자동화 한다.

 

@ModelAttribute를 적용한다면?

@ResponseBody
@PostMapping("/v2/tutor")
public String modelAttributeV2(
				@ModelAttribute Tutor tutor													
) {
		
	String name = tutor.getName();
	int age = tutor.getAge();

	return "tutor name = " + name + " age = " + age;
}

 

@ModelAttirubte 동작 순서

  1. 파라미터에 @ModelAttribute가 있으면 파라미터인 Tutor 객체를 생성한다.
  2. 요청 파라미터 이름으로 객체 필드의 Setter를 호출해서 바인딩한다.
    1. 파라미터 이름이 name 이면 setName(value); 메서드를 호출한다.
    2. 파라미터 이름과 필드 이름이 반드시 같아야 한다!

 

파라미터의 타입이 다른 경우

만약 요청 파라미터 age 에 int가 아닌 String 이 전달된다면?

BindException 발생

이런 경우 때문에 **Validation(검증)**이 필요하다.

 

@ModelAttirubte 생략

  • @ModelAttribute와 지난 시간에 배운 @RequestParam은 모두 생략이 가능하다.
@ResponseBody
@PostMapping("/v3/tutor")
public String modelAttributeV3(Tutor tutor) {

	String name = tutor.getName();
	int age = tutor.getAge();

	return "tutor name = " + name + " age = " + age;
}

 

Spring에서는 @RequestParam이나 @ModelAttribute가 생략되면

  • String, int, Integer 와 같은 기본 타입은 @RequestParam과 Mapping한다.
  • 나머지 경우들(객체)은 모두 @ModelAttribute 와 Mapping한다.

 

[Http Message Body에 값을 담아 요청하는 경우]

@RequestParam, @ModelAttribute는 GET + Query Parameter와, POST HTML Form Data를 바인딩하는 방법이다.

 

이제부터 배울 내용은 HTTP Message Body에 직접적으로 Data가 전달되는 경우이다.

  • Request Body의 Data를 바인딩하는 방법이다.

REST API에서 주로 사용하는 방식이다.

HTTP Method POST, PUT, PATCH에서 주로 사용한다.

  • GET은 Request Body가 존재할 수는 있지만 권장하지 않는다.

JSON, XML, TEXT 등을 데이터 형식으로 사용한다.

 

 

[바디에 text가 담긴 경우]

HTTP Request Body에 Data가 전송되는 경우 HttpMessageConverter를 통해 바인딩된다.

 

HttpServletRequest 예시

@Slf4j
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/v1/request-body-text")
  public void requestBodyTextV1(
          HttpServletRequest request,
          HttpServletResponse response
  ) throws IOException {

      ServletInputStream inputStream = request.getInputStream();
      String bodyText = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
      
      response.getWriter().write("response = " + bodyText);

  }

}

 

I/O 예시

@PostMapping("/v2/request-body-text")
public void requestBodyTextV2(
				InputStream inputStream,
				Writer responseWriter
) throws IOException { 
		
	String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
	
	responseWriter.write("response = " + bodyText);
}

 

HttpEntity 예시

  • HttpMessageConverter 사용 → 추후 설명
  • HttpEntity를 사용하면 HttpMessageConverter를 사용한다.
@PostMapping("/v3/request-body-text")
public HttpEntity<String> requestBodyTextV3(HttpEntity<String> httpEntity) { 
		
	// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
	String body = httpEntity.getBody();
		
	return new HttpEntity<>("response = " + body); // 매개변수 = Body Message
	
}

Spring의 HttpMessageConverter 덕분에 간편하게 Request Data에 접근할 수 있다.

  1. HttpEntity를 사용하면 HttpMessageConverter가 동작하여 자동으로 매핑된다.
  2. 요청 뿐만이 아닌 응답까지 HttpEntity 하나만으로 사용이 가능해진다.

[HttpEntity]

HttpEntity는 HTTP Header, Body 정보를 편리하게 조회할 수 있도록 만들어준다.

 

HttpEntity 역할

  1. Http Request Body Message를 직접 조회한다
  2. Request 뿐만 아니라 Response도 사용할 수 있도록 만들어준다.
  3. Response Header 또한 사용할 수 있다.
  4. Request Parameter를 조회하는 기능들과는 아무 관계가 없다.
  5. View를 반환하지 않는다.

HttpEntity를 상속받은 객체

  • RequestEntity<>
    • HTTP Request Method, URL 정보가 추가 되어있다.
  • ResponseEntity<>
    • HTTP Response 상태 코드 설정이 가능하다.
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/v4/request-body-text")
  public HttpEntity<String> requestBodyTextV4(RequestEntity<String> httpEntity) {

      // HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
      String body = httpEntity.getBody();
      // url, method 사용 가능

      return new ResponseEntity<>("response = " + body, HttpStatus.CREATED); // Body Data, 상태코드
  }

}

위 방법을 적용해도 불편하다.

  • Data를 httpEntity에서 꺼내어 사용해야 한다.

Spring은 Http RequestBody Message를 읽어서 String이나 Object로 자동으로 변환해준다. 이때 HttpMessageConverter가 사용된다.

 

[RequestBody]

Spring에서 @RequestBody, @ResponseBody 어노테이션을 사용하면 각각 Request, Response 객체의 Body에 편하게 접근하여 사용할 수 있다.

 

@Controller // @RestController = @Controller + @ResponseBody
public class RequestBodyStringController {
	
  @ResponseBody
  @PostMapping("/v5/request-body-text")
  public String requestBodyTextV5(
          @RequestBody String body,
          @RequestHeader HttpHeaders headers
  ) {
      // HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
      String bodyMessage = body;

      return "request header = " + headers + " response body = " + bodyMessage;
  }
}

 

@RequestBody

  • 요청 메세지 Body Data를 쉽게 조회할 수 있다.

@RequestHeader

  • 요청 헤더 정보 조회

@ResponseBody

  • 응답 메세지 바디에 값을 쉽게 담아서 전달할 수 있도록 해준다.
  • View가 아닌 데이터를 반환한다.

요약

  1. 요청 파라미터, HTML Form Data에 접근하는 경우
    • @RequestParam, @ModelAttribute 를 사용한다.
  2. Http Message Body에 접근하는 경우
    • @RequestBody를 사용한다. (JSON, XML, TEXT)

 

[바디에 json이 담긴 경우]

HttpServletRequest 사용 예시

@Data
public class Tutor {
	private String name;
	private int age;
}

@RestController
public class JsonController {
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/v1/request-body-json")
	public void requestBodyJsonV1(
				HttpServletRequest request, 
				HttpServletResponse response
	) throws IOException {

		// request body message를 Read
		ServletInputStream inputStream = request.getInputStream();
		// UTF-8 형식의 String으로 변환한다.
		String requestBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

		// String requestBody를 ObjectMapper를 사용하여 변환 "{\"name\":\"wonuk\", \"age\":10}"
		Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);
		
		// 응답
		response.getWriter().write("tutor" + tutor);
	}
}

HttpServletRequest를 사용하여 HTTP Message Body 데이터를 Read하여 문자로 변환한다.

문자로 만들어진 JSON을 Jackson 라이브러리의 objectMapper를 사용하여 Object로 변환

 

@RequestBody 사용예시

@RestController
public class JsonController {
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/v2/request-body-json")
  public String requesBodytJsonV2(@RequestBody String requestBody) throws IOException {

      Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);

      return "tutor.getName() = " + tutor.getName() + "tutor.getAge() = " + tutor.getAge();
  }
    
}

@RequestBody를 사용하여 HTTP Request Body의 Data에 접근한다.

 

ObjectMapper가 계속 반복되는데 @ModelAttribute처럼 객체로 바로 반환은 안되나요?

 

ObjectMapper 제거예시

@RestController
public class JsonController {
	
	@PostMapping("/v3/request-body-json")
	public String requestBodyJsonV3(@RequestBody Tutor tutor) {
		
		Tutor requestBodyTutor = tutor;

		return "tutor = " + requestBodyTutor;
	}
}

위 Controller가 동작하는 이유는 무엇인가요?

  • @RequestBody
    • @RequestBody 어노테이션을 사용하면 Object를 Mapping할 수 있다.
    • HttpEntity<>, @RequestBody를 사용하면 HTTPMessageConverter가 Request Body의 Data를 개발자가 원하는 String이나 Object로 변환해준다.
    • JSON to Object의 Mapping 또한 가능하다.
      • MappingJackson2HttpMessageConverter 의 역할
      • 쉽게 설명하면 HTTP Message Converter가 ObjectMapper를 대신 실행한다.

@RequestBody는 생략할 수 없다.

@Slf4j
@RestController
public class JsonController {
	
	@PostMapping("/v4/request-body-json")
  public String requestBodyJsonV4(Tutor tutor) { // @RequestBody 생략시 @ModelAttribute가 된다.

      Tutor requestBodyTutor = tutor;

      return "tutor.getName() = " + requestBodyTutor.getName() + " tutor.getAge() = " + requestBodyTutor.getAge();
  }
	
}

 

생략하면 @ModelAttribute가 된다.

  • 요청 파라미터를 처리하도록 설정된다.

Request Header의 contentType은 꼭 application/json 이여야 한다.

  • 위 설정 정보를 기반으로 MessageConverter가 실행된다.

 

HttpEntity 사용예시

@RestController
public class JsonController {
	
	@PostMapping("/v5/request-body-json")
  public String requestBodyJsonV5(
          HttpEntity<Tutor> httpEntity
  ) {
      // 값을 꺼내서 사용해야한다!
      Tutor tutor = httpEntity.getBody();

      return "tutor.getName() = " + tutor.getName() + " tutor.getAge() = " + tutor.getAge();
  }
	
}

HttpEntity<Tutor>

  • Generic Type으로 Tutor가 지정되어 있기 때문에 해당 Class로 반환된다.

 

@ResponseBody 예시

@Controller
public class JsonController {
	
	@ResponseBody // @RestController = @Controller + @ResponseBody
	@PostMapping("/v6/request-body-json")
    public Tutor requestJson(@RequestBody Tutor tutor) {
        return tutor;
  }
	
}

View를 조회하지 않고 Response Body에 Data를 입력해서 직접 반환한다.

요청 뿐만이 아니라 응답에도 HttpMessageConverter가 동작한다.

  • MappingJackson2HttpMessageConverter 적용
  • 응답 객체인 Tutor가 JSON으로 변환되어 반환된다.

HttpEntity를 사용해도 된다.

 

요약

  1. 요청 데이터는 @RequestBody를 사용해서 바인딩 하면 된다.
  2. @RequestBody 는 생략이 불가능하다.
    • @ModelAttribute가 적용되기 때문
  3. HttpMessageConverter 가 요청 응답 데이터를 모두 변환할 수 있다.
    • JSON은 MappingJackson2HttpMessageConverter 를 사용한다.
    • Request Header의 Content-Type은 application/json 이어야 한다.
      • Header로 어떤 Converter가 동작할지 판별한다.

[HTTPMessageConverter]

Spring Framework에서 HTTP 요청과 응답을 변환하는 인터페이스이다. 클라이언트와 서버 간에 데이터를 주고받을 때, 요청 데이터를 자바 객체로 변환하거나 자바 객체를 응답 본문으로 변환하는 역할을 수행한다.

 

HttpMessageConverter의 역할

  • 데이터를 Obejct로 변환한다. 대표적으로 JSON을 변환한다.

  • @RequestBody
    • 요청 데이터 + Request Header를 참고하여 Object로 변환한다.
      • HTTP Request Body(JSON Data) → Converter(Jackson) → Object
      • Reqeust Header → Content-Type : application/json(전달할 데이터 형식)
  • @ResponseBody
    • 응답 데이터 + Accept Header를 참고하여 원하는 데이터 형식으로 변환한다.
      • Object → Converter(Jackson) → HTTP Response Body(JSON Data)
      • Request Header → Accept : application/json(허용할 데이터 형식)

 

[Server에서 Client로 Data를 전달하는 방법]

Server에서 Client로 Data를 전달하는 방법은 정적 리소스, View Template, HTTP Message Body 세가지 방법이 있다.

 

  1. 정적 리소스
    • 정적인 HTML, CSS, JS, Image 등을 변경 없이 그대로 반환한다.
  2. View Template
    • SSR(Server Side Rendering)을 사용할 때 View가 반환된다.
  3. View Template
    • SSR(Server Side Rendering)을 사용할 때 View가 반환된다.

 

[정적 리소스]

 

웹 애플리케이션에서 변하지 않는 파일들을 의미한다. 예를 들어, HTML, CSS, JavaScript, 이미지 파일들(JPG, PNG, GIF) 등이 정적 리소스에 해당한다.

Spring Boot의 정적 리소스 경로

  • 아래 경로들에 정적 리소스가 존재하면 서버에서 별도의 처리 없이 파일 그대로 반환된다.
  1. /static
  2. /public
  3. /META-INF/resources
  4. src/main/resources
    1. /static

 

[View Template]

Spring에서는 Thymeleaf, JSP와 같은 템플릿 엔진을 사용해 View Template을 작성할 수 있고, View Template은 서버에서 데이터를 받아 이를 HTML 구조에 맞게 삽입한 후, 최종적으로 클라이언트에게 전송되는 HTML 문서로 변환하여 사용자에게 동적으로 생성된 웹 페이지를 제공한다.

 

View Template

  • View Template은 Model을 참고하여 HTML 등이 동적으로 만들어지고 Client에 응답된다.
  • Spring Boot는 기본적으로 View Template 경로(src/main/resources/templates)를 설정한다.
  • build.gradle에 Thymeleaf 의존성을 추가하면 ThymeleafViewResolver와 필요한 Spring Bean들이 자동으로 등록된다.

@Controller의 응답으로 String을 반환하는 경우

@Controller
public class ViewTemplateController {
	
	@RequestMapping("/response-view")
  public String responseView(Model model) {
      // key, value
      model.addAttribute("data", "sparta");

      return "thymeleaf-view";
  }
	
}
  •  
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
    <h1>Thymeleaf</h1>
    <h2 th:text="${data}"></h2>
</body>
</html>

 

 

 

[HTTP Message Body(응답)]

REST API를 만드는 경우 Server에서 Client로 HTML을 전달하는 방식이 아닌 HTTP Message Body에 직접 Data를 JSON 형식으로 담아 전달한다.

정적 HTML, View Template 또한 HTTP Message Body에 담겨서 전달된다. 현재 설명하는 Response의 경우는 정적 HTML, View Template을 거치지 않고 직접 HTTP Response Message를 만들어 전달하는 경우를 말하는것.

 

HttpServletResponse 사용

@Controller
public class ResponseBodyController {
	
	@GetMapping("/v1/response-body")
	public void responseBodyV1(HttpServletResponse response) throws IOException {
		
		response.getWriter().write("data");
	
	}
}

 

 

ResponseEntity<> 사용

@GetMapping("/v2/response-body")
public ResponseEntity<String> responseBodyV2() {
		
	return new ResponseEntity<>("data", HttpStatus.OK);
}

Response Body에 data라는 문자열과 HttpStatus.OK에 해당하는 상태 코드를 반환한다.

ResponseEntity는 HttpEntity 를 상속받았다.

  • HttpEntity는 HTTP Message의 Header, Body 모두 가지고 있다.

 

@ResponseBody(TEXT, JSON) 사용

@Data
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 전체 필드를 인자로 가진 생성자
public class Tutor {

    private String name;
    private int age;

}
// TEXT 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-text")
public String responseBodyText() {
		
	return "data"; // HTTP Message Body에 "data"
}
// JSON 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-json")
public Tutor responseBodyJson() {
		
	Tutor tutor = new Tutor("wonuk", 100);
		
	return tutor; // HTTP Message Body에 Tutor Object -> JSON
}

View를 사용하는 것이 아닌 HTTP Message Converter를 통해 HTTP Message Body를 직접 입력할 수 있다. → ResponseEntity와 같음

 

@ResponseStatus 를 사용하여 상태 코드를 지정할 수 있다.

@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/v4/response-body")
public Tutor responseBodyV4() {
		
	Tutor tutor = new Tutor("wonuk", 100);
		
	return tutor;
}

단, 응답 코드를 조건에 따라서 동적으로 변경할 수는 없다.

ex) 1번의 경우 OK, 2번의 경우 Created

 

ResponseEntity<Object>(JSON)

@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
		
	Tutor tutor = new Tutor("wonuk", 100);
	
	return new ResponseEntity<>(tutor, HttpStatus.OK);
}

ResponseEntity<>두 번째 파라미터에 Enum을 사용하여 상태 코드를 바꿀 수 있다.

HTTP Message Converter를 통하여 JSON 형태로 변환되어 반환된다.

동적으로 응답 코드를 변경할 수 있다.

@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
		
	Tutor tutor = new Tutor("wonuk", 100);
	
	if (조건) {
		return new ResponseEntity<>(tutor, HttpStatus.OK);
	} else {
		return new ResponseEntity<>(tutor, HttpStatus.BAD_REQUEST);
	}
	
}

 

정리

Client에서 Server로 Data를 전달하는 세가지 방법

 

1. GET - Query Param, Query String

ex) http://localhost:8080/tutor?name=wonuk&age=100

@RequestParam, @ModelAttribute 사용

 

2. POST - HTML Form(x-www-form-urlencoded)

POST /form-data
content-type: application/x-www-form-urlencoded

key1=value1&key2=value2

@RequestParam, @ModelAttribute 사용

 

3. HTTP Request Body

데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 사용

@RequestBody 사용

 

Server(Spring)에서 HTTP 응답을 Client에 전달하는 세가지 방법

 

1. 정적 리소스

정적인 HTML, CSS, JS, Image 등을 변경없이 그대로 반환한다.

해당 파일 이름을 논리적으로 반환

 

2. View Template

SSR(Server Side Rendering)을 사용할 때 View가 반환된다. (동적으로 View 생성해 반환)

해당 파일 이름을 논리적으로 반환

@Controller 사용

 

3. HTTP Message Body

응답 데이터를 직접 Message Body에 담아 반환한다.

@ResponseBody, ResponseEntity<Object> 사용 (상태코드는 @ResponseStatus 사용 그러나 조건별로 사용 불가)