단의 개발 블로그
사용자 요청 처리하기 본문
폼 제출 처리하기
Sumbit your taco 버튼을 클릭하면 사용자 에러가 발생한다. 우리가 작성한 HTML에서 <form> 태그를 살펴보면 method 속성이 POST로 되어있다. 해당 설정이 되면 브라우저는 데이터를 모아서 요청 경로에 POST 방식으로 전송한다. 그러면 서버에서는 해당 요청과 데이터를 처리하는 컨트롤러 메소드가 있어야 한다. 이전에 작성한 DesignTacoController.java에 새롭게 추가한다.
...
@PostMapping
public String processDesign(Taco design) {
//post 요청을 처리하는 메소드다.
log.info("Processing design: " + design);
return "redirect:/orders/current";
}
...
- @PostMapping 어노테이션은 /design 경로의 POST 요청을 처리함을 나타낸다.
- processDesign함수의 Taco로 전달 받은 파라미터로 데이터가 바인딩 된다.
- HTML에서 checkbox 요소가 여러개인데, ingredients이름을 가지며 텍스트 요소의 이름은 name으로 되어있다. 이 필드가 ingredients와 바인딩된다.
- 반환 값은 리디렉션되는데, 변경된 경로로 재 접속하여 뷰를 나타낸다. /orders/current
주문 요청을 반환된 값을 요청 받는 컨트롤러(OrderController)와 domain(Order)도 생성한다.
OrderContoller.java
@Log4j2
@Controller
@RequestMapping("/orders")
public class OrderController {
@GetMapping("/current")
public String orderForm(Model model) {
model.addAttribute("order", new Order());
return "orderForm";
}
@PostMapping
public String processOrder(Order order) {
log.info("Order submitted: " +order);
return "redirect:/";
}
}
- /orders로 시작되는 경로의 요청을 이 컨트롤러의 요청 메소드가 처리한다. 클래스 상단에서 RequestMapping으로 전역 처리를 하고, 세부 요청 별로 처리하기 위해서 GetMapping, PostMapping 등 메소드 별로 나눈다.
- 작업이 완료되면 각 뷰를 반환한다.
Order.java
@Data
public class Order {
private String deliveryName;
private String deliveryStreet;
private String deliveryCity;
private String deliveryState;
private String deliveryZip;
private String ccNumber;
private String ccExpiration;
private String ccCVV;
}
주문을 요청 받아 제출하는 HTML도 추가한다.
orderForm.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<form method="POST" th:action="@{/orders}" th:object="${order}">
<h1>Order your taco creations!</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<a th:href="@{/design}" id="another">Design another taco</a><br/>
<div th:if="${#fields.hasErrors()}">
<span class="validationError">
Please correct the problems below and resubmit.
</span>
</div>
<h3>Deliver my taco masterpieces to...</h3>
<label for="deliveryName">Name: </label>
<input type="text" th:field="*{deliveryName}" />
<br/>
<label for="deliveryStreet">Street address: </label>
<input type="text" th:field="*{deliveryStreet}" />
<br/>
<label for="deliveryCity">City: </label>
<input type="text" th:field="*{deliveryCity}" />
<br/>
<label for="deliveryState">State: </label>
<input type="text" th:field="*{deliveryState}" />
<br/>
<label for="deliveryZip">Zip code: </label>
<input type="text" th:field="*{deliveryZip}" />
<br/>
<h3>Here's how I'll pay...</h3>
<label for="ccNumber">Credit Card #: </label>
<input type="text" th:field="*{ccNumber}" />
<br/>
<label for="ccExpiration">Expiration: </label>
<input type="text" th:field="*{ccExpiration}" />
<br/>
<label for="ccCVV">CVV: </label>
<input type="text" th:field="*{ccCVV}" />
<br/>
<input type="submit" value="Submit order"/>
</form>
</body>
</html>
프로젝트를 실행 시키고, 타코 화면이 나오면 폼을 제출한다. 그러면 아래와 같은 출력을 확인할 수 있다.


주문도 마찬가지로 입력하고 제출한다. 아래와 같이 로그가 잘 출력된다.

사용자 입력 유효성 검사
사용자가 악성 요청이나, 데이터 타입에 맞지 않는 전송을 할 경우 시스템에 오류가 발생한다. 유효성 검사를 하려면 검사규칙을 선언하고, 컨트롤러에 수행하는 것을 지정한다. 그리고 검사 에러를 폼에서 보여주면 된다. 유효성 검사 API에는 몇가지 어노테이션을 제공한다. 여기서는 해당 어노테이션을 사용하여 유효성을 검사한다.
build.gradle
...
implementation 'org.springframework.boot:spring-boot-starter-validation'
...
Taco.java
...
@NotNull
@Size(min=5, message = "Name must be at least 5 character long")
private String name;
@Size(min=1, message = "You must choose at least 1 ingredient")
private List<String> ingredients;
...
- name 필드는 null여서는 안된다.
- name 필드는 최소 5글자 이상이여야 한다.
- ingredient는 최소 1개이상 선택되어야 한다.
Order.java
...
@NotBlank(message = "Name is required")
private String deliveryName;
@NotBlank(message = "Street is required")
private String deliveryStreet;
@NotBlank(message = "City is required")
private String deliveryCity;
@NotBlank(message = "State is required")
private String deliveryState;
@NotBlank(message = "Zip code is required")
private String deliveryZip;
@NotBlank(message = "Not a valid credit card Number")
private String ccNumber;
@Pattern(regexp = "^(0[1-9]|1[0-2])([\\/])([1-9][0-9])", message = "Must be formatted MM/YY")
private String ccExpiration;
@Digits(integer = 3, fraction = 0, message = "Invalid CVV")
private String ccCVV;
...
- delivery관련 내용은 모두 빈칸이여선 안된다.
- ccNumber는 Luhn 알고리즘 검사에 합격한 신용카드 번호여야 한다.
- ccExpiration에는 정규표현식을 사용하여 MM/YY 형식이 맞는지 검사한다.
- ccCVV에는 입력값이 정확히 세자리 숫자인지 검사한다.
DesignTacoController.java
...
@PostMapping
public String processDesign(@Valid Taco design, Errors errors) {
if (errors.hasErrors())
{
return "design";
}
log.info("Processing design: " + design);
return "redirect:/orders/current";
}
...
- Taco 객체의 유효성 검사를 수행한다.
- 에러가 발생하면 Error 객체에 내용을 담아서 뷰에 전달한다.
OrderController.java
...
@PostMapping
public String processOrder(@Valid Order order, Errors errors) {
if (errors.hasErrors()){
return "orderForm";
}
log.info("Order submitted: " +order);
return "redirect:/";
}
...
만약 서버에서 에러가 전달되면 뷰에서도 해당 내용을 보여줘야한다. html을 수정한다.
orderForm.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="EUC-KR">
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<form method="POST" th:action="@{/orders}" th:object="${order}">
<h1>Order your taco creations!</h1>
<img th:src="@{/images/TacoCloud.png}" /> <a th:href="@{/design}"
id="another">Design another taco</a><br />
<div th:if="${#fields.hasErrors()}">
<span class="validationError"> Please correct the problems
below and resubmit. </span>
</div>
<h3>Deliver my taco masterpieces to...</h3>
<label for=" deliveryName">Name: </label>
<input type="text" th:field="*{deliveryName}" />
<span class="validationError"
th:if="${#fields.hasErrors('deliveryName')}"
th:errors="*{deliveryName}">Name Error</span>
<br />
<label for="deliveryStreet">Street address: </label>
<input type="text" th:field="*{deliveryStreet}" />
<span class="validationError"
th:if="${#fields.hasErrors('deliveryStreet')}"
th:errors="*{deliveryStreet}">Street Error</span>
<br />
<label for="deliveryCity">City: </label>
<input type="text" th:field="*{deliveryCity}" />
<span class="validationError"
th:if="${#fields.hasErrors('deliveryCity')}"
th:errors="*{deliveryCity}">City Error</span>
<br />
<label for="deliveryState">State: </label>
<input type="text" th:field="*{deliveryState}" />
<span class="validationError"
th:if="${#fields.hasErrors('deliveryState')}"
th:errors="*{deliveryState}">State Error</span>
<br />
<label for="deliveryZip">Zip code: </label>
<input type="text" th:field="*{deliveryZip}" />
<span class="validationError"
th:if="${#fields.hasErrors('deliveryZip')}"
th:errors="*{deliveryZip}">Zip Error</span>
<br />
<h3>Here's how I'll pay...</h3>
<label for="ccNumber">Credit Card #: </label>
<input type="text" th:field="*{ccNumber}" />
<span class="validationError"
th:if="${#fields.hasErrors('ccNumber')}"
th:errors="*{ccNumber}">CC Num Error</span>
<br />
<label for="ccExpiration">Expiration: </label>
<input type="text" th:field="*{ccExpiration}" />
<span class="validationError"
th:if="${#fields.hasErrors('ccExpiration')}"
th:errors="*{ccExpiration}">CC Num Error</span>
<br />
<label for="ccCVV">CVV: </label>
<input type="text" th:field="*{ccCVV}" />
<span class="validationError"
th:if="${#fields.hasErrors('ccCVV')}"
th:errors="*{ccCVV}">CC Num Error</span>
<br />
<input type="submit" value="Submit order" />
</form>
</body>
</html>
design.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="EUC-KR">
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="@{/images/TacoCloud.png}" />
<form method="POST" th:object="${taco}">
<span class="validationError"
th:if="${#fields.hasErrors('ingredients')}"
th:errors="*{ingredients}">Ingredient Error</span>
<div class="grid">
<div class="ingredient-group" id="wraps">
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox"
th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="proteins">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input name="ingredients" type="checkbox"
th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="cheeses">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox"
th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox"
th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
<div class="ingredient-group" id="sauces">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox"
th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}" />
<span
class="validationError" th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name Error</span>
<br />
<button>Submit your taco</button>
</div>
</form>
</body>
</html>
유효성 검사가 완료되지 않을 경우 아래 화면이 출력된다.

메인 화면 추가하기
만약 메인화면이 데이터를 주고 받거나, 사용자의 요청을 처리하지 않는 페이지라면 간단하게 처리할 수 있다.
프로젝트 경로 ... /config/WebConfig.java에 파일을 생성하고 아래 내용을 추가한다.
WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
Home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="EUC-KR">
<title>Taco Cloud</title>
</head>
<body>
<h1>Welcome to...</h1>
<img th:src="@{/images/TacoCloud.png}"/>
</body>
</html>
</html>
루트 경로를 요청할 경우 아래 화면만 출력된다.

출처
https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=239755024&start=slayer
'Web > Spring' 카테고리의 다른 글
| JPA (1) | 2024.09.05 |
|---|---|
| 데이터 베이스 (0) | 2024.09.04 |
| 간단한 웹 요청 만들기 (0) | 2024.05.08 |
| 스프링 요청 및 라이브러리 살펴보기 (1) | 2024.05.02 |
| 프로젝트 생성 및 프로젝트 구조 (0) | 2024.05.02 |