왜 비동기 통신을 먼저 배워야 할까?
웹 페이지를 새로고침하지 않고 일부 내용만 바꿔 사용자 경험을 높이는 것이 요즘 표준입니다. 매번 새로고침을 하는 것이 사용자에게 부담이 되기도 하고 매번 데이터의 변동량이 많기도 하며 자칫 DDOS와 같은 효과가 일어날 수도 있기 때문입니다.
서버와의 통신을 비동기적으로 처리하면 페이지 응답성이 좋아지고, 사용자 행동에 즉각적으로 반응할 수 있습니다. JSP 기반 레거시·신규 프로젝트 모두에서 이런 통신 방법을 모르면 실무에서 고생하기 쉽습니다.
저 또한 실무에 있어 AJAX를 사용해야 했습니다. 얼마 전 체계보완요청으로 요건 개발 건이 있었습니다. 해당 개발 건의 핵심이 AJAX였죠. 저도 공부도 하고 이해는 하고 있었지만 실무에서 유지보수 규칙에 어긋나지 않게 쓰려 하니 쉽지 않았습니다. 그래도 해당 개발 건을 진행하면서 또 한 단계 성장할 수 있었죠.
이번 글은 JSP(또는 서블릿)과 함께 사용하는 비동기 통신 기법을 25가지 핵심 포인트로 정리한 것입니다.
각 항목은 “무엇을”, “왜 중요한지”, “실무에서 어떻게 쓰는지(코드)”로 구성했습니다. 입문자 분들에게 많은 도움이 되길 바랍니다.

1. 엔드포인트 설계: /api/*로 REST 규칙 따르기
JSP/서블릿 URL을 /api/users, /api/posts처럼 명확히 분리합니다.
클라이언트에서 호출하기 쉽고 CORS/Security 설정이 용도별로 구분되기 때문입니다.
@WebServlet("/api/users")
public class UserServlet extends HttpServlet { ... }
2. JSON 반환 기본(서블릿) — Content-Type과 인코딩 설정
서버는 response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); 설정
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.print("{\"id\":1, \"name\":\"홍길동\"}");
out.flush();
3. Gson 같은 라이브러리로 객체 → JSON 자동 변환
수동 문자열보다 JSON 사용을 권장합니다.
User u = new User(1,"홍길동");
String json = new Gson().toJson(u);
response.getWriter().write(json);
4. 클라이언트에서 Fetch API 기본 사용법 (모던 방법)
fetch()로 GET/POST 요청입니다.
fetch('/app/api/users')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
5. POST로 JSON 전송하기 (fetch + 서버 수신)
요청 시 Content-Type: application/json 설정
fetch('/app/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({name: '이영희'})
});
예(서버): new Gson().fromJson(request.getReader(), User.class)
6. 폼 전송을 AJAX로 바꾸기 (preventDefault)
<form> submit 이벤트를 가로채서 비동기 전송
form.addEventListener('submit', e => {
e.preventDefault();
const data = new FormData(form);
fetch('/app/api/upload', { method:'POST', body: data });
});
7. 파일 업로드: FormData와 서블릿의 Part 사용
무엇: 멀티파트 전송(FormData) + request.getPart()로 처리
예(클라이언트): formData.append('file', fileInput.files[0]);
예(서버): Part p = request.getPart("file"); p.write(savePath);
8. XMLHttpRequest(구형) vs fetch(권장)
구형 XHR은 IE 지원이 필요할 때 사용합니다. 요즘은 fetch + Promise/async-await 권장합니다.
예(XHR): xhr.open('GET', url); xhr.send();
9. jQuery.ajax 사용 팁(레거시 프로젝트)
jQuery가 이미 포함된 프로젝트라면 활용 가능합니다.(AJAX의 가장 대표적인 활용 형태입니다.)
$.ajax({
url:'/app/api',
method:'GET',
dataType:'json',
success: data => { /*...*/ }
});
10. 응답 캐싱 제어(브라우저/서버)
캐시가 문제면 서버에서 Cache-Control: no-cache 또는 클라이언트에서 fetch(url, { cache: 'no-store'}) 구문으로 요청합니다. 데이터의 최신성을 보장하기 위함입니다.
11. 에러 처리(HTTP 상태코드에 따른 분기)
4xx/5xx 에러 처리에 대하여 사용자 친화적 메시지를 표시합니다.
fetch(url).then(res => {
if (!res.ok) throw new Error(res.status);
return res.json();
}).catch(err => showError(err));
12. 타임아웃과 요청 취소(AbortController)
느린 요청 취소로 UX 개선을 요구합니다.
const controller = new AbortController();
fetch(url, { signal: controller.signal });
setTimeout(()=>controller.abort(), 5000);
13. 동시 요청 제어 (Promise.all / race)
여러 API를 병렬로 호출하여 화면의 초기화 속도를 증가시킵니다.
예: Promise.all([fetch(a), fetch(b)]).then(...)
14. 응답 포맷: JSON 권장, XML은 레거시만 사용
JSON이 가볍고 파싱이 쉽습니다. 최근 들어 정보처리기사에서도 JSON 출제 빈도가 늘어나는 추세입니다.
15. 인증 헤더 처리 (JWT 등)
Authorization: Bearer <token>로 토큰 전송하여 인증을 받는 방식입니다.
fetch('/app/api/protected', {
headers: { 'Authorization': 'Bearer ' + token }
});
16. CSRF 대비 (쿠키 기반 인증 시)
쿠키+세션 사용 시 CSRF 토큰 검증이 필요합니다. 그렇기에 AJAX를 요청할 경우 CSRF 토큰을 헤더로 포함하거나 SameSite 설정을 권장합니다.
17. CORS(교차 출처) 문제 해결
API 서버의 응답에 Access-Control-Allow-Origin 설정이 필요합니다. 다만 보안성 검토를 위해 허용 도메인을 제한합니다.
18. 인코딩(UTF-8) 주의 — 한글 깨짐 방지
서버/응답/DB 모두 UTF-8로 통일
예: response.setCharacterEncoding("UTF-8"); + JSP <meta charset="UTF-8">
19. 컨텍스트 루트 사용법 (EL 사용)
JSP에서 contextPath를 동적으로 사용하면 경로를 하드코딩하는 불상사를 피할 수 있습니다.
예(JSP): <script>const ctx='${pageContext.request.contextPath}'</script>
클라이언트: fetch(ctx + '/api/users')
20. 폴링 vs 서버 푸시(WebSocket) 선택 기준
자주 변경되는 실시간 데이터는 WebSocket을 고려하고, 단순 업데이트는 폴링(혹은 SSE)로 구현합니다.
21. 디바운스/스로틀로 불필요 요청 줄이기 (검색박스 등)
사용자의 빠른 입력에 따라 다수 요청 발생을 방지합니다.
예: lodash _.debounce 또는 직접 setTimeout 제어
22. UI 피드백(로딩 스피너)과 실패 복구 UX
요청 중 로딩을 표시합니다 또한, 실패 시 재시도 버튼을 제공하여 편의성을 높입니다.
23. 페이징/무한스크롤 처리(서버와의 약속)
서버에서 page/limit 파라미터를 받아 JSON으로 반환합니다. 그러면 클라이언트는 append 처리하여 마무리합니다.
24. 파일 다운로드(비동기 방식) — Blob 처리
파일을 비동기로 받아서 브라우저에서 바로 다운로드 처리합니다.
fetch('/api/file')
.then(r => r.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'file.pdf'; a.click();
});
25. 테스트 및 디버깅 도구: 브라우저 DevTools / Postman
네트워크 탭으로 요청/응답 확인과 Postman으로 서버의 엔드 포인트를 테스트합니다.
실전 예제 — 간단한 사용자 목록 CRUD (JSP + 서블릿 + fetch)
서버(서블릿) — /api/users (GET 리스트)
@WebServlet("/api/users")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
List<User> list = userService.findAll();
String json = new Gson().toJson(list);
resp.getWriter().write(json);
}
}
클라이언트(index.jsp)
<script>
const ctx='${pageContext.request.contextPath}';
fetch(ctx + '/api/users')
.then(res => res.json())
.then(list => {
const tbody = document.querySelector('#user-tbody');
tbody.innerHTML = list.map(u => `<tr><td>${u.id}</td><td>${u.name}</td></tr>`).join('');
});
</script>
외부 참고자료 (신뢰 링크 3개)
- MDN – Fetch API (fetch 사용법, Response 처리 등)
https://developer.mozilla.org/ko/docs/Web/API/Fetch_API - MDN – XMLHttpRequest (구형 XHR 설명)
https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest - OWASP – Cross-Site Request Forgery (CSRF) 방어 (보안 체크)
https://owasp.org/www-community/attacks/csrf
마무리 — 실무로 바로 가져가자
지금 배운 25가지 레시피는 JSP 기반 프로젝트에서 바로 적용 가능한 팁들입니다. 특히 인코딩/경로(contextPath)/CSRF/CORS/토큰 관리 같은 실수는 초급자가 실무에서 자주 겪는 이슈랍니다. 각 항목을 잘 기억해 두었다가 실무에서 하나씩 적용하면서 직접 테스트를 해보는 것을 권장합니다. 작은 실습이 큰 실력을 만듭니다.
[필수용어 실무] 이전 편을 보지 못했다면? 필수용어 실무 시리즈 바로가기
[필수용어 실무 1편] 비전공자가 실무에서 바로 활용 가능한 데이터 연동 & 서버 흐름 25선
[필수용어 실무 2편] 실무에서 마주치는 예외 처리 & 트러블슈팅 핵심 25선
[필수용어 실무 3편] JSP & MVC 패턴 실무 흐름 완전정복 25선
[필수용어 실무 4편] JSP 데이터 처리 & EL/JSTL 실무활용 25선