Redirect

Spring Controller에서 Redirect 처리하는 방법에 대해 살펴본다

Redirect란?

Redirect란 클라이언트(브라우저)에게 재접속을 지시하는 행위를 말한다. 예를 들어 다음과 같은 상황이라고 볼 수 있다.

ex) 고객센터에 걸려온 전화

(상담원) 안녕하십니까? Hacademy입니다.

(고객) 안녕하세요. 열혈강사 채널에 광고 문의하려고 전화했는데요

(상담원) 네 그러십니까. 죄송하지만 열혈강사 채널 관련 문의는 다른 팀에서 전담하고 있는데요. (번호를 알려준다) 이 번호로 다시 연락 주시겠습니까? (Redirect 처리)

(고객) 네 알겠습니다. (안내받은 번호로 전화를 건다)

Spring Controller에서의 Redirect

Spring Controller에서는 필요에 따라 Redirect를 사용할 수 있다. 사용하는 방법은 여러 가지가 있다.

  1. HttpServletResponse 객체를 사용하는 방법

  2. URL에 redirect 접두사를 표기하는 방법

  3. RedirectView를 사용하는 방법

Redirect가 필요한 상황

Redirect가 필요한 상황을 만들기 위해 boot04redirect 프로젝트를 생성한다. 간단한 환율 계산기를 만들 예정이다.

Controller와 Mapping 구현

우리가 만들 기능은 두 가지이다.

  • /usd2krw - 달러($)를 원화(₩)로 변환하여 출력하는 기능 (View 없이 구현)

  • /krw2usd - 원화(₩)를 달러($)로 변환하여 출력하는 기능 (View 없이 구현)

Controller 구현

우선 ExchangeController라는 클래스를 생성한다.

ExchangeController.java
@Controller
public class ExchangeController {

}

Mapping 구현

작성 시점의 환율로 계산하여 사용자에게 출력하도록 Mapping을 구현한다.

작성 시점인 2022.08.29 기준 환율은 다음과 같다

  • 1 KRW = 0.00074 USD

  • 1 USD = 1347.15 KRW

ExchangeController.java
@GetMapping("/usd2krw")
@ResponseBody
public String usd2krw(@RequestParam double usd) {
	double krw = 1347.15 * usd;
	return usd + "(USD) → "+krw+"(KRW)";
}
@GetMapping("/krw2usd")
@ResponseBody
public String krw2usd(@RequestParam double krw) {
	double usd = 0.00074 * krw;
	return krw+"(KRW) → "+usd+"(USD)";
}

접속 확인

Spring Boot App을 실행한 뒤 다음 주소로 접속하여 정상적으로 작동되는지 확인한다. 각각 100달러와 10000원에 대한 환전 결과이다.

통합 Mapping 구현

지금은 서로 다른 URL로 각각 KRW→USD, USD→KRW 를 처리하고 있으나 이를 하나의 URL로 합쳐서 처리할 수도 있지 않을까 하는 의문이 든다. 이를테면 주소에 mode라는 이름의 파라미터를 추가하여 하고자 하는 작업을 알려주는 방법을 사용할 수 있다.

  • http://localhost:8080/exchange?mode=usd2krw&amount=100

  • http://localhost:8080/exchange?mode=krw2usd&amount=10000

가능하다면 한 페이지에 많은 기능이 있는 것이 좋으므로 /exchange라는 주소를 만들고, 대신 mode에 따라 /usd2krw/krw2usdredirect 하도록 처리한다. 그림으로 표현하면 다음과 같다.

HttpServletResponse 사용

/exchange1 매핑을 만들어 mode, amount 파라미터를 수신하고 redirect 처리하도록 구현하는 Mapping은 다음과 같다. Redirect는 JSP/Servlet 기본 응답 객체인 HttpServletResponse에서 지원하므로 매개변수에 추가하며, IOException이 발생하므로 예외 전가 처리까지 해야 한다.

@GetMapping("/exchange1")
@ResponseBody
public void exchange1(@RequestParam String mode, @RequestParam double amount, HttpServletResponse response) throws IOException {
	if(mode.equals("usd2krw")) {
		response.sendRedirect("usd2krw?usd="+amount);
	}
	else if(mode.equals("krw2usd")) {
		response.sendRedirect("krw2usd?krw="+amount);
	}
}

현재 페이지에서는 사용자에게 반환할 내용이 없으므로 반환형은 void를 사용하였다. 접속하면 다음과 같이 Redirect 처리 됨을 확인할 수 있다. 자세히 살펴볼 내용은 개발자 도구에서 기록이 남는다는 것과, 주소가 /exchange가 아니라는 점이다.

크롬 개발자 도구에서 네트워크 이력을 확인하면 다음과 같이 Redirect가 되었음을 확인할 수 있다.

Redirect 접두사 사용

Spring에서는 redirect: 접두사로 redirect 처리가 가능하다. 단, View Resolver를 사용해야 한다. /exchange2 를 만들어 redirect 접두사를 사용하여 처리하도록 구현한다.

@GetMapping("/exchange2")
public String exchange2(@RequestParam String mode, @RequestParam double amount) {
	if(mode.equals("usd2krw")) {
		return "redirect:usd2krw?usd="+amount;
	}
	else if(mode.equals("krw2usd")) {
		return "redirect:krw2usd?krw="+amount;
	}
	return null;
}

접속 테스트를 하면 정상적으로 Redirect가 이루어지는 것을 확인할 수 있다.

RedirectView 사용

Spring에서는 Redirect만을 위한 RedirectView 클래스를 제공한다. /exchange3을 만들고 테스트를 위한 코드를 작성한다.

@GetMapping("/exchange3")
public RedirectView exchange3(@RequestParam String mode, @RequestParam double amount) {
	if(mode.equals("usd2krw")) {
		return new RedirectView("usd2krw?usd="+amount);
	}
	else if(mode.equals("krw2usd")) {
		return new RedirectView("krw2usd?krw="+amount);
	}
	return null;
}

반환형으로 RedirectView를 설정하고 객체를 반환하도록 코드를 구성한다. 객체 내부에는 Redirect 목표 매핑 주소를 작성한다.

테스트 시 정상적으로 Redirect 됨을 확인할 수 있다.

RedirectAttributes

Redirect 할 때 파라미터를 작성하는 것이 번거로운 경우가 많다. 파라미터는 ?와 &를 사용하여 작성해야 하므로 수식이 지저분해지는데, RedirectAttributesModel과 동일한 방법으로 Redirect시 파라미터를 처리할 수 있도록 Spring에서 제공하는 도구이다.

매개변수에 선언한 뒤 Model처럼 key=value 형식으로 데이터를 추가한다. redirect 접두사RedirectViewSpring에서 지원하는 기능에서 사용할 수 있다.

Redirect 접두사 사용 시

@GetMapping("/exchange2-1")
public String exchange2_1(@RequestParam String mode, @RequestParam double amount, RedirectAttributes attr) {
	if(mode.equals("usd2krw")) {
		attr.addAttribute("usd", amount);
		return "redirect:usd2krw";
	}
	else if(mode.equals("krw2usd")) {
		attr.addAttribute("krw", amount);
		return "redirect:krw2usd";
	}
	return null;
}

RedirectView 사용 시

@GetMapping("/exchange3-1")
public RedirectView exchange3_1(@RequestParam String mode, @RequestParam double amount, RedirectAttributes attr) {
	if(mode.equals("usd2krw")) {
		attr.addAttribute("usd", amount);
		return new RedirectView("usd2krw");
	}
	else if(mode.equals("krw2usd")) {
		attr.addAttribute("krw", amount);
		return new RedirectView("krw2usd");
	}
	return null;
}

주의사항

Redirect를 처리할 경우의 주의사항은 다음과 같다.

절대 경로에 대한 처리

  • JSP/Servlet 방식일 경우 절대경로를 사용하려면 HttpServletRequest 객체를 통한 계산이 필요하다(1번 방식)

    • 상대 경로 - response.sendRedirect("usd2krw?usd=100")

    • 절대 경로 - response.sendRedirect(request.getContextPath()+"/usd2krw?usd=100")

  • Spring에서 제공하는 방식(2번, 3번 방식)일 경우 경로 앞에 /를 적으면 절대경로로 인식한다.

    • redirect 접두사(2번 방식)를 사용하는 경우

      • 상대 경로 - return "usd2krw?usd=100"

      • 절대 경로 - return "/usd2krw?usd=100";

    • RedirectView를 사용하는 경우

      • 상대 경로 - return new RedirectView("usd2krw?usd=100")

      • 절대 경로

        • return new RedirectView("/usd2krw?usd=100")

        • return new RedirectView("usd2krw?usd=100", true)

Redirect는 Get방식이다

  • 원칙적으로 POST방식처럼 파라미터를 숨겨서 전송하는 것이 불가능하다.

  • 예외적으로 HttpSession을 사용하는 Flash value 방식을 지원한다.

    • (주의) Flash value는 새로고침 시 소멸한다.

소스 코드

Github

ExchangeController

ExchangeController.java
package com.hacademy.boot04.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;

@Controller
public class ExchangeController {
	@GetMapping("/usd2krw")
	@ResponseBody
	public String usd2krw(@RequestParam double usd) {
		double krw = 1347.15 * usd;
		return usd + "(USD) → "+krw+"(KRW)";
	}
	@GetMapping("/krw2usd")
	@ResponseBody
	public String krw2usd(@RequestParam double krw) {
		double usd = 0.00074 * krw;
		return krw+"(KRW) → "+usd+"(USD)";
	}
	//[1] HttpServletResponse를 사용한 Redirect
	@GetMapping("/exchange1")
	@ResponseBody
	public void exchange1(@RequestParam String mode, @RequestParam double amount, HttpServletResponse response) throws IOException {
		if(mode.equals("usd2krw")) {
			response.sendRedirect("usd2krw?usd="+amount);
		}
		else if(mode.equals("krw2usd")) {
			response.sendRedirect("krw2usd?krw="+amount);
		}
	}
	//[2] Redirect prefix를 사용한 Redirect
	@GetMapping("/exchange2")
	public String exchange2(@RequestParam String mode, @RequestParam double amount) {
		if(mode.equals("usd2krw")) {
			return "redirect:usd2krw?usd="+amount;
		}
		else if(mode.equals("krw2usd")) {
			return "redirect:krw2usd?krw="+amount;
		}
		return null;
	}
	//[2-1] Redirect prefix를 사용한 Redirect + RedirectAttributes 사용
	@GetMapping("/exchange2-1")
	public String exchange2_1(@RequestParam String mode, @RequestParam double amount, RedirectAttributes attr) {
		if(mode.equals("usd2krw")) {
			attr.addAttribute("usd", amount);
			return "redirect:usd2krw";
		}
		else if(mode.equals("krw2usd")) {
			attr.addAttribute("krw", amount);
			return "redirect:krw2usd";
		}
		return null;
	}
	//[3] RedirectView를 사용한 Redirect
	@GetMapping("/exchange3")
	public RedirectView exchange3(@RequestParam String mode, @RequestParam double amount) {
		if(mode.equals("usd2krw")) {
			return new RedirectView("usd2krw?usd="+amount);
		}
		else if(mode.equals("krw2usd")) {
			return new RedirectView("krw2usd?krw="+amount);
		}
		return null;
	}
	//[3-1] RedirectView를 사용한 Redirect + RedirectAttributes 사용
	@GetMapping("/exchange3-1")
	public RedirectView exchange3_1(@RequestParam String mode, @RequestParam double amount, RedirectAttributes attr) {
		if(mode.equals("usd2krw")) {
			attr.addAttribute("usd", amount);
			return new RedirectView("usd2krw");
		}
		else if(mode.equals("krw2usd")) {
			attr.addAttribute("krw", amount);
			return new RedirectView("krw2usd");//상대경로
//			return new RedirectView("krw2usd", true);//절대경로
		}
		return null;
	}
}

Last updated