| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- bean
- 티스토리챌린지
- Java
- 동적 SQL
- MDN
- redis
- jscode
- TypeScript
- open contribution jam
- Database
- 글또
- 체험
- react
- 부꾸러미
- dto projection
- 프로그래머스
- 사이드 프로젝트
- 코드트리
- SQL
- MDN Web Docs
- 구슬
- 테오의 스프린트
- 후기
- 보따리
- 부꾸
- Spring
- spring context
- 글또 #다짐
- 오블완
- jooq
- Today
- Total
벤티의 개발 로그
[React] 브라우저: '너의 OS는.' 본문
'Android? iOS?'

웹 브라우저에서 사용자가 어떤 모바일 OS로 접속했는지 판단할 수 있을까?
이번에 맡은 업무에서 가장 많이 고민하는 내용이다. 한 번도 하지 않았던 고민이었다.
기존에는 하나의 모바일 OS를 대상으로만 서비스를 제공했었으나, 이번에 다른 쪽이 추가 되면서 OS에 따라 기존 코드를 분기 처리하거나, 새로운 코드를 추가할 일이 많아졌다.
그래서 이번 기회에 이 내용에 대해 확실하게 알고 가기로 했다.
HTTP Header
[Network] #2 HTTP
오늘은 OSI 7계층과 TCP/IP 4계층 양쪽 모두에서 최상위 계층에 있는 Application Layer, 그 중에서도 HTTP에 대해 정리해보려고 한다! Application Layer에서는 SMTP나 FTP 같이 메일이나 파일을 전송할 수 있는
ventilog.tistory.com
예전에 컴퓨터네트워크 스터디를 하면서 썼던 글에 해답이 있었다.
이 중 HTTP Header에 대해 더 자세히 알아보았다. 출처에 따르면, Header의 종류는 아래와 같이 크게 4개로 나눌 수 있다.
1. General Header: 요청과 응답 모두에 적용되지만 바디에서 최종적으로 전송되는 데이터와는 관련이 없는 헤더이다.
2. Request Header: 요청에서 사용되지만 메시지의 컨텐츠와 관련이 없는 패치될 리소스나 클라이언트 자체에 대한 자세한 정보를 포함하는 헤더이다.
3. Response Header: 위치 또는 서버 자체에 대한 정보(이름, 버전 등)와 같이 응답에 대한 부가적인 정보를 갖는 헤더이
4. Entity Header: 컨텐츠 길이나 MIME 타입과 같이 엔티티 바디에 대한 자세한 정보를 포함하는 헤더이다.
이번에 나를 도와주었던 것은 바로 2번, Request Header다. Request Header의 예시는 아래와 같다. (코드블럭이 JSON을 지원하지 않아 JavaScript를 사용했다.)
GET /home.html HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/testpage.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT
If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a"
Cache-Control: max-age=0
핵심은 User-Agent 속성이었다.
User-Agent
User-Agent 속성은 사용자 에이전트의 애플리케이션 타입, OS, 소프트웨어 벤더 또는 소프트웨어 버전을 식별할 수 있는 특성 문자열을 포함한다.

현재 맡고 있는 프로젝트가 Web View 환경이어서 호환성에 대해 좀 더 찾아보았다. 이전 업무에서 특정 기능을 구현했음에도 불구하고 테스트 과정에서 작동하지 않아, 이번에도 일부 기능이나 외부 서비스에서 User-Agent로 사용자의 접속 환경을 판별하는 로직이 적용되지 않을까 걱정했지만 다행히 모든 브라우저와 호환되는 속성이었다.
따라서, 이 정보를 통해 사용자가 접속하는 브라우저의 종류뿐만 아니라, 사용자가 접속하는 OS까지 파악할 수 있다고 판단하여 이 속성의 값으로 분기 처리를 해서 로직을 구현하기로 했다.
User-Agent: 브라우저 별 값
흥미로웠던 것은 모바일 OS 뿐만 아니라, 브라우저에 따라서도 User-Agent 값이 다르다는 것이다.
예를 들어, 대다수의 리눅스에서 기본 브라우저로 설정된 Firefox와 윈도우에서 많이 쓰이는 Chrome, Edge 브라우저의 User-Agent 값은 모두 다르다. Firefox는 'Firefox', Chrome은 'Chrome', Edge는 'Edg'라는 문자열을 포함한다.
하지만, 일반적으로 브라우저에 따라서 분기 처리를 하지는 않는다고 한다. MDN 문서에 따르면, 이유는 아래와 같다.
웹은 사용하는 브라우저나 장치와 관계없이 모든 사람이 접근할 수 있도록 만들어졌습니다. 특정 브라우저를 대상으로 하기보다는 기능의 가용성을 기반으로 웹사이트를 점진적으로 개선하도록 웹사이트를 개발하는 방법이 있습니다.
쉽게 생각하면, 만약 내가 사용하는 서비스가 Chrome이랑 Edge에서 서로 다른 UI/UX를 제공할 경우, 아주 불편할 것이다.
(다행히 내가 원하는 것은 사용자가 사용하는 모바일 OS에 따라 분기 처리를 하는 것이므로, 브라우저별로 로직을 다르게 구현할 일은 없을 것 같다!)
User-Agent: 모바일 OS 별 값
Android와 iOS는 서로 다른 User-Agent 값을 가지고 있다. 물론 각 기기에서 어떤 브라우저로 실행하느냐에 따라서 정확한 값은 천차만별이지만, Android는 'Android', iOS는 기기의 종류에 따라 'iPad, iPhone, iPod' 문자열을 공통으로 포함한다.
따라서 웹 브라우저에서도 HTTP Header에 담긴 User-Agent에 둘 중 어느 문자열이 포함되어 있는지로 사용자의 모바일 OS를 판단할 수 있다.
모바일 OS 판별 코드
MDN 문서에 작성된 예시 코드(출처)는 아래와 같다.
const UA = navigator.userAgent;
const isWebkit =
/\b(iPad|iPhone|iPod)\b/.test(UA) &&
/WebKit/.test(UA) &&
!/Edge/.test(UA) &&
!window.MSStream;
// 이하 생략
앞서 작성한 대로, 'iPad, iPhone, iPod' 문자열의 포함 여부로 iOS인지를 판단하고 있었다. Apple은 iOS의 모든 브라우저가 내부적으로 Webkit을 사용하도록 강제하기 때문에 MDN 문서에 해당 코드를 작성한 것 같다.
그래서 예시 코드와 조사한 내용을 참고하여 아래와 같은 코드를 작성했다. (실제로 적용한 코드는 아래와 다르다.)
const detectPlatform = () => {
const userAgent = navigator.userAgent;
if (/android/i.test(userAgent)) {
return 'android';
}
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return 'ios';
}
return 'unknown';
};
다행히 React에서도 navigator를 사용하면 어렵지 않게 User-Agent의 문자열에 포함된 값으로 모바일 OS를 판별하는 로직을 구현할 수 있었다.
결론 & 앞으로...
이번 업무를 통해 MDN 문서를 참고해서 필요한 내용을 정리하는 경험을 쌓을 수 있었고, HTTP에 대해 더 깊게 공부해야겠다고 판단했다.
그리고 다행히 구현은 어렵지 않았지만, 한 가지가 마음에 걸렸다. 바로 앞으로 프로젝트 전반적으로 너무 많은 곳에서 해당 로직이 필요할 것이므로 중복 코드가 증가할 것이라는 점이다. 중복 코드를 어떻게 줄일지 좀 더 고민해야겠다! (가능하다면 컴포넌트를 만들어서 사용할 것 같다...)