데브코스 백엔드 3기 36일차
수목금!
WebApplicationContext, REST API, CORS 등을 배웠다
WebApplicationContext
ApplicationContext 상속 + Servlet Context에 접근할 수 있는 기능 추가
- Servlet Context
- 객체
- 여러 Servlet이 공유하는 정보가 담겨있음
- Servlet Container에서 만들어짐
사진출처 - https://velog.io/@rlawogus73/Java-Spring-
- Servlet은 Servlet Context에 접근해서
RootApplicationContext 가져와서
Servlet에 만들어진 Application Context와 부모자식간의 관계를 만들어줌
RootApplicationContext
- 모든 WebApplicationContext가 접근 가능한 WebApplicationContext(편의상 RootApplicationContext라고 부르는)
사진출처- https://velog.io/@dongeranguk/DispatcherServlet-%EC%84%A4%EC%A0%95
- MVC와 분리되어 있으니 MVC관련 설정할 필요x (이건 ServletApplicationContext에서)
(ServletApplicationContext는 DispatcherServlet이 직접 사용하는 Controller를 포함한 Web 관련 Bean을 등록하는 데 사용)
(모놀리식 아키텍쳐에서는 통재로 하나의 Application Context에 등록) - Service, DB 관련만
- Servlet Context가 만들어질 때 같이 만들어짐
RootApplicationContext 만들어지는 방법
- web.xml
- ContextLoaderListener 사용해서 만들기
1) WebApplicationInitializer를 구현한 클래스에
2) WebConfig 설정(prefix / suffix 위한 viewResolver만 Bean으로 추가)해주고(RootConfig.java)
public class TaeheeWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
var rootApplicationContext = new AnnotationConfigWebApplicationContext();
rootApplicationContext.register(RootConfig.class);
var loaderListener = new ContextLoaderListener(rootApplicationContext);
servletContext.addListener(loaderListener);
REST(ful) API
REST + API
RESTful API : REST 아키텍쳐 스타일 충족 시
- API : 다른 다양한 소프트웨어 component들과 통신하기 위한 방법
- 그 때 통신 규약(protocol) 필요
- 그 규약이 REST로 정의 된 게 REST API
- web상에서 resource 전달 위한 것
- SOAP에서 전달하면 HTTP 사용한 것
- REST아키텍셔 따르면 REST API
- REST(Representational State Transfer) 아키텍쳐
- client-server
- stateless
- cache (위의 세가지는 HTTP와 유사)
- uniform interface(균일한 인터페이스)
- layered system
- code-on-demand
uniform interface
- identification of resources
- URI(고유한 식별자)를 통해 자원을 식별해야 한다
- 자원은 객체, 객체란 언제든 변할 수 있으니 현재 상태만으로는 구별 불가 -> 식별자 필요
- manipulation of resources through representations
- 자원의 특정한 상태를 표현해야 -> 표현에는 다양한 방식가능(문서, 파일, HTTP 메시지 엔티티 등)
- 서버는 요청에 해당하는 데이터의 상태를 표현(보내줌)
- HATEOAS
annotation
@RequestBody
: 요청메시지를 우리가 원하는 형태로 변환@ResponseBody
: 우리가 정의한 모델클래스가 response body 형태로@RestController
: 클래스의 모든 메서드에 @ResponseBody 적용된것과 동일
=> Http Message Converter가 동작해서 Http message로 변환해줌
Http Message Converter
class ServletConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
var messageConverter = new MarshallingHttpMessageConverter();
var xStreamMarshaller = new XStreamMarshaller();
messageConverter.setMarshaller(xStreamMarshaller); //xml로 바꾸기
messageConverter.setUnmarshaller(xStreamMarshaller); //xml 받은걸 자바객체로 인스턴스화
converters.add(0, messageConverter);
}
- imple WebMvcConfigurer
- override extendMessageConverters()
- MarshallingHttpMessageConverte 이용
- MessageConverter가 default로 하던것에 xml로 바꾸는것 추가 : XStreamMarshaller
CORS
SPA(Single Page Application)
버튼 누르면 화면에서 그려져야 할 부분을 DOM programming 통해 브라우저에서 동적으로 그림
첫 요청하고 html 로드
-> 그 이후부터 client는 ajax 기술이용해서 back단에 http 요청보냄
-> 서버가 Json 응답줌
-> client가 그 응답 parsing해서 읽음 + 데이터 가지고 DOM programming 이용해서 화면 랜더링
- 일반 web application : URL 변경 시 모든 페이지 다시 랜더링
- 단일 페이지 web application : URL 변경 시 DOM 조작 통해 특정 영역만 랜더링
CORS error
Single Page 만들 때 가장 많이 생기는 에러
클라이언트와 서버의 도메인이 달랐을 때 보안 상의 이유로 응답을 받지 못하도록 막은 것
사진출처 - https://junhyunny.github.io/information/react/react-proxy/
- 사용자를 악의적인 행위에서 보호하기 위해서 브라우저는 동일출처(same origin)에서만 리소스 접근 허용(=same origin policy)
=> Web Application은 동일한출처의 리소스만 접근 가능 but 다양한 출처의 리소스 접근이 필요해!
이걸 허용하게 해주는게 CORS(Cross Origin Resource Sharing)
Http 이용해서 Http의 다른 출처의 리소스에 접근할 수 있게 하는 메커니즘
동일한 출처란?
- host / protocol / port 모두 동일
- 경로만 다름
- 이 때, host는 ip같음 아니고 도메인명 동일해야(가상호스팅 있으니까)
CORS error 해결하기
1. Front에서 Proxy 이용
(서버와 클라이언트가 로컬호스트를 사용하고 있다는 전제하에 동작)
- proxy 이용해서 “http://localhost:8080”으로 바꿔줘서
(CORS erro는 Client-Server 간에만 일어남 브라우저에서 거부하는거니까, 서버-서버간에는 일어나지 않음)
2. WebMvcConfigurer 에서 설정
class ServletConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedMethods("GET", "POST")
.allowedOrigins("http://front-server.com");
}
3. @CrossOrigin 사용
Controller에서 설정
@CrossOrigin(origins = "http://front-server.com")
public class TaeheeController {
Preflight
단순 요청(Simple request)이 아니라면 사전 요청(PreFlight)로 CORS 가능여부 확인
- Access-Control-Request-Method, Access-Control-Request-Headers, Origin 3가지의 HTTP request headers를 사용
- OPTIONS method 사용
Spring Web -> SpringBoot Web으로
-
Spring에서 DataSource @Bean 으로 등록했던것
-> SpringBoot에선 application.yml에 가능 -
view이름에 대한 매핑(viewResolver 설정)
imple WebApplicationInitializer에서 configureViewResolver()
-> application.yml에thymeleaf: ~
- Servlet Context path 지정도 application.yml에
- logging 설정도(logback.xml 그대로 사용해도됨)
- imple WebApplicationInitializer + imple webMvcConfiguration해서 만든 WebConfig 안의 내용들
->@SpringBootApplication
안에@SpringBootConfiguration
안에@Configuration
=> TaeheeApplication.java 자체가 configuration- TaeheeApplication imple WebApplicationInitializer 하고 설정 해줘도되고
- 따로 클래스 MvcConfig.java 만들어도 되고, 대신
@@Configuration
@Configuration @ComponentScan(basePackages = "org.prgrms.kdt) MvcConfig imple imple WebApplicationInitializer
spring:
datasource:
url: jdbc:mysql://localhost/order_mgmt
username: root
password: root1234!
thymeleaf:
view-names: "views/*"
prefix: "/WEB-INF/"
server:
servlet:
context-path: /kdt
logging:
level:
org:
springframework: DEBUG
==> SpringBoot의 AutoConfiguration 기능 덕분에 가능
설정이 자동으로 됨 - 어떻게?
=> @SpringBootApplication
안에 @EnableAutoConfiguration
에 의해 제공
오늘의 테코톡
- 11/16 티거의 Web server vs WAS
- 11/17 정의 REST API
- REST API의 HATEOAS 까지 부합하려면,, 이렇게까지 해서 굳이 REST가 필요할까?
=> Fielding : “시스템을 통제할 수 있다고 생각한다면 REST에 부합하는지 아닌지에 대해 시간 낭비하지 말자” - 자원은 마치 객체와 같고 시간에 따라 그 상태(state)가 변화할 수 있다
- 특정 시점에 자원이 지니고 있는 상태를 특정한 형식으로 표현한 걸 Client와 Server가 서로 전송
- 메시지를 읽는 주체들(Client와 Server사이 component들(기계들))에게 스스로에 대해 설명해야함
- 자기 서술적 메시지 : Host header에 도메인명 기재 필요(나는 어디로 보낸 요청이다)
- 가상호스팅으로 인해 IP만으로는 안됨 도메인쓰기(하나의 IP 주소에 복수의 도메인명 존재 가능)
- REST API의 HATEOAS 까지 부합하려면,, 이렇게까지 해서 굳이 REST가 필요할까?
- 11/18 나봄의 CORS
- CORS 강의 복습 느낌!
일기(회고)
- 정말 힘들었던 한주였다
그래도 드디어 한가지가 끝났기 때문에 앞으로 더 집중 할 수 있다!
해낸 나야 고생했어 너무 뿌듯하다ㅋㅎ
훈팀미팅
-
훈님 괌 여행으루(부러워) 이번주는 월요일에 진행했다
ㅅㅍㅅ님과 ㅇㅍ님도 중간에 자리를 빛내주셨다^_^
- 훈님은 db 테스트를 어떻게 하고 계신가요?
- db 테스트를 한다고 하면 크게 3가지 방법이 있어요.
- embedded mysql
- test-container
설정들에 대해 별도로 공부해야하는 공부 비용(?)이 듭니다 - docker-compose
db docker를 띄워서 compose해서 사용합니다.
어차피 docker 공부를 해야 하니까 test하는김에 docker도 공부하게 되니 가장 선호하는 방법입니다.
- db 테스트를 한다고 하면 크게 3가지 방법이 있어요.
- 바우처 지갑과 관련하여 wallet controller, service는 클래스를 만들고, repository는 vouchers 테이블에서 데이터를 가져오기 때문에 VoucherRepository를 이용하였습니다.
CRUD 기준으로 wallet repository를 하나 더 만들어야 했을까요? wallet 도메인을 만들기에는 매핑만 되는 느낌입니다.
- wallet 도메인이 있어도 되고 없어도 되는 상황같아요.
일단, 현재 wallet 도메인이 없는 상황에서 wallet 관련 layer가 있는 상황은 이상한것 같아요.
도메인에 대한 구체적인 정의가 필요합니다.
- wallet 도메인이 있어도 되고 없어도 되는 상황같아요.
- 출력메시지 내용을 출력을 담당하는 콘솔 클래스에서 결정을 해줘야 할 것같다고 생각했는데, 그렇게 되니 메시지 내용마다 메서드를 생성해야해서 잘못된 방향이라는 생각이 듭니다.
제가 잘못 생각했다는 확신을 하고 싶은데 조언해주실 수 있나요?- 네, 그렇게 되면 콘솔이 모든 메시지와 관련 내용을 알아야 하게 되니까 최상단에 위치하는 느낌이 되겠네요. (번쩍!! 그러네요!)
- 단위테스트 작성 중, 다른 테이블과 관련이 있는 데이터의 경우 데이터를 준비하는 given 코드가 길어집니다.
예를 들면 fk로 인해 다른 테이블에 데이터가 있어야 테스트가 가능한 경우, given에서 해당 데이터를 insert 하는 코드를 넣게 되어 길어집니다. 혹시 제가 참고할 정보가 있을까요?
- ㅎ님) 배보다 배꼽이 더 커지는 경우죠.
sql문을 모아두는 파일에 insert문 쿼리를 같이 작성해두는 방법을 사용하면 되지 않을까요? - ㄷㅎ님) 저는 테스트코드 짤 때 해당 기능에서 필요한 객체들을 다 보여주는 걸 좋아해서 길더라도 다 적는 편입니다.
- ㅎ님) 배보다 배꼽이 더 커지는 경우죠.