MVC 활용(3) : HTTP 요청 맵핑하기 2부 - URI 패턴 맵핑

3. HTTP 요청 맵핑하기 2부 - URI 패턴 맵핑

URI, URL, URN 햇갈린다

여러개의 문자열로 매핑할 수도 있다.

@Controller
public class SampleController {

    @RequestMapping({"/hello", "/hi"})
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello"))
               .andDo(print())
               .andExpect(status().isOk());
               
        mockMvc.perform(get("/hi"))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

요청 식별자로 맵핑하기

  • @RequestMapping은 다음의 패턴을 지원한다.
  • ? : 한 글자 (“/author/???” => “/author/123”)
  • * : 여러 글자 (“/author/*” => “/author/keesun”)
  • ** : 여러 패스 (“/author/** => “/author/keesun/book”)

한글자만 맵핑

@Controller
public class SampleController {

    @GetMapping("/hello?")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello1"))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

Path가 있고 그 다음에 한 글자가 올 수도 있다.

@Controller
public class SampleController {

    @GetMapping("/hello/?")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/1"))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

여러 글자가 오는 경우

@Controller
public class SampleController {

    @GetMapping("/hello/*")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/123"))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

여러 Path가 오는 경우

@Controller
public class SampleController {

    @GetMapping("/hello/**")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/123/456"))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

클래스에 선언한 @RequestMapping과 조합

  • 클래스에 선언한 URI 패턴뒤에 이어 붙여서 맵핑한다.
@Controller
@RequestMapping("/hello")
public class SampleController {

    @GetMapping("/**")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/123/456"))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

정규 표현식으로 맵핑할 수도 있다.

  • /{name:정규식}
@Controller
@RequestMapping("/hello")
public class SampleController {

    @RequestMapping("/{name:[a-z]+}")
    @ResponseBody
    public String hello(@PathVariable String name){
        return "hello " + name;
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/spring"))
               .andDo(print())
               .andExpect(status().isOk())
               .andExpect(content().string("hello spring"));
    }
}

테스트 코드2 : 영문자가 아닌 숫자를 넣으면 테스트가 깨진다.

@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/123"))
               .andDo(print())
               .andExpect(status().isOk())
               .andExpect(content().string("hello spring"));
    }
}

패턴이 중복되는 경우에는?

  • 가장 구체적으로 맵핑되는 핸들러를 선택한다.
@Controller
@RequestMapping("/hello")
public class SampleController {

    @RequestMapping("/spring")
    @ResponseBody
    public String hellospring(){
        return "hello spring";
    }
    
    @RequestMapping("/*")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

테스트 코드

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/spring"))
               .andDo(print())
               .andExpect(status().isOk())
               .andExpect(content().string("hello spring"));
    }
}

테스트 코드2 : 어떤 핸들러에 매핑되었는지 알고싶다.

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/spring"))
               .andDo(print())
               .andExpect(status().isOk())
               .andExpect(content().string("hello spring"))
               .andExpect(handler().handlerType(SampleController.class))
               .andExpect(handler().methodName("hellospring"));
    }
}

URI 확장자 맵핑 지원

  • 이 기능은 권장하지 않는다. (스프링 부트에서는 기본으로 이 기능을 사용하지 않도록 설정 해 줌)
    - 보안 이슈 (RFD Attack)
    - URI 변수, Path 매개변수, URI 인코딩을 사용할 때 할 때 불명확 함.

스프링 MVC는 암묵적으로 다음과 같은 매핑을 해준다.

@Controller
@RequestMapping("/hello")
public class SampleController {

    @RequestMapping({"/spring", "/spring.*"})
    @ResponseBody
    public String hellospring(){
        return "hello spring";
    }
}

스프링 MVC에서는 spring.json, spring.html, spring.xml도 처리할 수 있게끔 해준다.

테스트 코드 : 스프링 부트 X, DispatcherServlet /app으로 매핑

@Controller
public class helloController {
    
    @Autowired
    HelloService helloService;
    
    @GetMapping("/hello")
    @ResponseBody
    public String hello(){
        return "Hello, " + helloService.getName();
    }
}
@Service
public class HelloService {
    
    public String getname(){
        return "spring";
    }
}

이를 톰캣에서 실행해보면 http://localhost:8080/app/hello, http://localhost:8080/app/hello.json도 실행된다.

RFD Attack

테스트 코드 : 스프링 부트

@Controller
@RequestMapping("/hello")
public class SampleController {

    @RequestMapping("/spring")
    @ResponseBody
    public String hellospring(){
        return "hello spring";
    }
}
@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/spring.json"))
               .andDo(print())
               .andExpect(status().isOk())
               .andExpect(content().string("hello spring"))
               .andExpect(handler().handlerType(SampleController.class))
               .andExpect(handler().methodName("hellospring"));
    }
}
@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/spring.json"))
               .andDo(print())
               .andExpect(status().isNotFound());
            
    }
}

최근의 추세는 Accept 헤더에 어떤 타입을 원한다고 지정을 해두면 확장자를 보지 않더라도 서버가 헤더에 있는 내용을 보고 판단할 수 있다.

또한 헤더가 불편하면 파라미터로 지정하는 방법도 있다. ?type=

또한 확장자가 없는 요청과 같이 지원할 수도 있다. 그러나 이 방법은 권장하지 않는다.

@Controller
@RequestMapping("/hello")
public class SampleController {

    @RequestMapping({"/spring", "/spring.*"})
    @ResponseBody
    public String hellospring(){
        return "hello spring";
    }
}
@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;
	
    @Test
    public void helloTest() throws Exception{
        mockMvc.perform(get("/hello/spring.xml"))
               .andDo(print())
               .andExpect(status().isOk());
        
        mockMvc.perform(get("/hello/spring"))
               .andDo(print())
               .andExpect(status().isOk());
    }
}

참고

좋은 웹페이지 즐겨찾기