WebApp 안에 다른 WebApp 넣어서 보여주기
최근 몇 일간 이미 만들어진 WebApp A안에 다른 WebApp B이 하나의 페이지처럼 들어가 보이도록 하는 작업을 했다. 사용자는 A에는 접근할 수 있지만, B에는 (대부분) 접근할 수 없는 상태.
정석(?)은 사실 이런 식으로 해야 할 것 같지만:
- B를 RESTful API 혹은 어떤 web API를 갖는 서비스로 작성하고 (Service B)
- B의 RESTful API를 보여주는 view에 해당하는 WebApp을 작성하고 (App B)
- A는 ‘Service B’를 써서 특정 페이지를 만든다
하지만 그렇게 하지는 않고, 여름에 인턴으로 잠시 일했던 puzzlet군이 사용했던 방법을 좀 고쳐서 썼다.
Web B가 html5로 작성한거라서 http proxy 비슷한거 만들기는 편했다.
인증
다행히 이 두 웹앱은 인증 서비스는 공유한다. 그래서 A에서 인증한 shared-secret을 가지고 만든 데이터를 B에게 주고 이걸로 인증하게 했다. 그리고 B에 접속할 떄 사용하는 세션 키를 A의 세션에 저장하고 꺼내서 쓰게 했다.
페이지 접근
A 의 특정 URL 밑에 오는 것은 B의 URL로 접근하게 했다. 다만 일부 접근할 수 없는 URI에 대한 처리는 있다. 이런게 없었다면 그냥 reverse proxy쓰거나 하면 될 듯도.
web_app_A_host/page_B/...
이면 실제론 B의 페이지에 접근하게 했다. 실제론 URI가 완전히 맵핑되는 건 아니고, 특정 변환을 해주긴해야 해서 reverse-proxy를 쓰진 않았다.
HTTP 응답 rewrite
http 응답은 몇 가지 조건을 걸고 rewrite했다.
1. http 헤더에 Location 필드가 있는 경우
30x나 201, 202인 경우 이게 붙어서 오는데, 이번 경우엔 201/202는 안 써서 그냥 30x에 Location field를 rewrite하게 했다.
2. html 페이지
href, src 등으로 오는 것 전부 rewrite.
그리고 나서 css, js, 실제 내용에 해당하는 부분을 찾았다. 그리곤 다음과 같이 합쳐서 내보냈다. html5lib이 xpath 로 DOM에 접근할 수 있어서 엄청 간단히 했다.
3. css 추가 처리
CSS의 경우 골치 아픈 문제가 생기더라. B에서 온 CSS 셀렉터에 해당하는게 A 안에도 있어서 레이아웃까지 깨지고 있었다. Orz.
그래서 위에서 한 것처럼 특정 class 밑에 B의 내용이 오게 강제하고, B에서 오는 모든 css 에는 셀렉터마다 (…) 맨 앞에 저 embedded가 붙게 했다. cssutils로 해석한 다음 각 셀렉터를 하나하나 업데이트하는 코드를 짰다.
약간 느려지긴 하던데 어차피 저건 정적으로 캐싱할 수 있으니까 신경 끄기로 (야).
결론(?)
- http 요청하는 클라이언트 흉내내기는 재밌더라
- CSS 때문에 귀찮아.
- B가 사실 상 페이지 1개짜리 앱이니 그냥 했지, 안 그랬으면 RESTful API로 서비스 분리하는게 훨씬 속 편할 것 같다.