공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 08 / 22

Vue 3 SPA 와 Java 서버 연동

정적 서빙, 같은 출처 API, 폐쇄망 라이브러리
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

6. 연습 문제

이 레슨의 세 파일에 직접 코드를 덧붙이는 문제 5개입니다. 정답을 보기 전에 먼저 힌트만 보고 스스로 작성해 보세요.

문제 1. 정적 서빙에 nosniff 와 ETag 추가

StaticHandler.send 에 X-Content-Type-Options: nosniff 헤더와, 파일 내용의 해시로 만든 ETag 헤더를 추가하세요. 힌트: 해시는 MessageDigest.getInstance("SHA-256") 로 바이트 배열을 넣고 앞 8바이트만 16진수로 잘라 씁니다.

java
static void send(HttpExchange ex, int status, String type, byte[] body) throws IOException {
    ex.getResponseHeaders().set("Content-Type", type);
    ex.getResponseHeaders().set("X-Content-Type-Options", "nosniff");
    if (status == 200) {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(body);
        StringBuilder etag = new StringBuilder("\"");
        for (int i = 0; i < 8; i++) etag.append(String.format("%02x", hash[i]));
        ex.getResponseHeaders().set("ETag", etag.append("\"").toString());
    }
    ex.sendResponseHeaders(status, body.length);
    try (OutputStream os = ex.getResponseBody()) { os.write(body); }
}

nosniff 는 브라우저가 Content-Type 을 무시하고 내용을 추측 실행하는 것을 막습니다. ETag 는 파일이 안 바뀌었으면 재전송을 줄이는 조건부 요청(If-None-Match)의 기반입니다.

문제 2. 완료된 항목만 보기 필터

app.js 에 computed 로 완료된 항목만 걸러내는 doneTodos 를 추가하고, 화면에 필터 토글 버튼을 넣어 전체·완료만 전환하세요. 힌트: 원본 배열은 그대로 두고, 화면에는 필터를 적용한 파생 값만 보여줍니다.

javascript
const showDoneOnly = ref(false);
const visibleTodos = computed(() =>
    showDoneOnly.value ? todos.value.filter(t => t.done) : todos.value);
// 반환 목록에 showDoneOnly, visibleTodos 추가
// 템플릿에서는 v-for="t in visibleTodos" 로 교체

원본 todos 는 그대로 두고 computed 로 파생 목록만 만들면, 필터를 꺼도 원본 데이터가 보존됩니다.

문제 3. 제목 더블클릭 편집

목록 항목의 제목을 더블클릭하면 입력창으로 바뀌고, Enter 를 누르면 PATCH /api/todos/{id} 로 제목을 수정하는 기능을 추가하세요. 힌트: 편집 중인 항목의 id 를 별도 ref 로 기억해 조건 분기에 씁니다.

html
<span v-if="editingId !== t.id" @dblclick="editingId = t.id">{{ t.title }}</span>
<input v-else v-model="editTitle" @keyup.enter="saveTitle(t)" @blur="editingId = null">
javascript
const editingId = ref(null);
const editTitle = ref('');
const saveTitle = (t) => run(async () => {
  await api('PATCH', `/api/todos/${t.id}`, { title: editTitle.value });
  editingId.value = null;
});

TodoApi.PATCH 는 이미 title 을 부분 수정으로 받으므로 서버 코드는 그대로 재사용됩니다.

문제 4. StaticHandler 에 gzip 압축 추가

요청 헤더 Accept-Encoding 에 gzip 이 있으면 응답을 GZIPOutputStream 으로 압축해 보내세요. Vue 라이브러리처럼 큰 파일에서 전송량이 크게 줄어듭니다. 힌트: 압축 여부와 상관없이 Content-Length 는 실제로 보낼 바이트 수와 같아야 합니다.

java
String accept = ex.getRequestHeaders().getFirst("Accept-Encoding");
if (accept != null && accept.contains("gzip")) {
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    try (GZIPOutputStream gz = new GZIPOutputStream(buf)) { gz.write(body); }
    ex.getResponseHeaders().set("Content-Encoding", "gzip");
    body = buf.toByteArray();
}
ex.sendResponseHeaders(status, body.length);

Accept-Encoding 을 안 보내는 클라이언트에는 압축을 적용하면 안 되므로, 이 검사가 빠지면 구형 클라이언트가 응답을 해석 못 합니다.

문제 5. 라이브러리 버전 폴더 구조로 바꾸기

static/js/plugin/vue.global.prod.js 를 static/js/plugin/vue-3.5.13/vue.global.prod.js 로 옮기고, index.html 의 <script src> 경로를 갱신하세요. 버전을 올릴 때는 어떤 절차가 필요한지 설명하세요.

html
<!-- index.html -->
<script src="/js/plugin/vue-3.5.13/vue.global.prod.js"></script>

새 버전을 받으면 plugin/vue-3.5.14/ 폴더를 새로 만들고 index.html 의 경로만 바꿉니다. 경로 자체가 달라지므로 브라우저가 옛 캐시를 참조할 일이 없고, 문제가 생기면 경로를 되돌려 즉시 롤백할 수 있습니다.

연습 문제
    이전 섹션5 자주 하는 실수 (Tip)6 / 7다음 섹션7 정리