No updates detected yet. GitHub releases will appear here.
Loading the full README. The saved excerpt is shown below.
김포·김해·제주·청주공항에 갈 때 어느 주차장에 댈지 출발 직전에 판단하기 위한 휴대폰용 웹앱. 여객 주차장 13곳을 다룬다. https://airport-parking-korea.vercel.app — 암호를 아는 사람만 들어간다. 같은 공항이라도 주차장별 상황이 크게 다르다. 2026-09-15 10:13 실측에서 김포 국내선 제1주차장은 잔여 0면이었지만 국제선 주차빌딩은 161면이 비어 있었다. 요청이 올 때만 도는 프록시 하나가 전부다. 저장소도, 스케줄러도 없다. 공공데이터포털이 해외 IP를 차단한다. GitHub Actions 러너(미국 애리조나)에서 apis.data.go.kr로의 TCP 443이 거부가 아니라 드롭된다 — connect=0.000000s 후 타임아웃. 같은 러너에서 api.github.com은 0.16초에 응답했으므로 러너 네트워크 문제가 아니다. 서울(icn1)에서는 141ms에 응답한다. vercel.json의 regions를 바꾸면 앱 전체가 죽는다. "오늘 곡선"을 그리려면 24시간 도는 수집기가 필요하고, 그건 상시 켜진 기기나 유료 인프라를 뜻한다. 둘 다 두지 않기로 했다.Read the full README ↗
김포·김해·제주·청주공항에 갈 때 어느 주차장에 댈지 출발 직전에 판단하기 위한 휴대폰용 웹앱. 여객 주차장 13곳을 다룬다.
https://airport-parking-korea.vercel.app — 암호를 아는 사람만 들어간다.
같은 공항이라도 주차장별 상황이 크게 다르다. 2026-09-15 10:13 실측에서 김포 국내선 제1주차장은 잔여 0면이었지만 국제선 주차빌딩은 161면이 비어 있었다.
요청이 올 때만 도는 프록시 하나가 전부다. 저장소도, 스케줄러도 없다.
휴대폰 ──▶ Vercel (서울 icn1)
├─ public/ 정적 파일
├─ api/login.py 암호 확인 → 세션 쿠키
└─ api/parking.py 원천 호출 → 등급·접근성 결합 → JSON
│
▼
apis.data.go.kr
api/ Vercel 서버리스 함수 (진입점)
collector/ 공유 로직 — api(호출·파싱) auth(인증) grading(등급) live(응답 조립)
data/ 주차장별 도보·셔틀 접근성 (API로 오지 않는 정적 정보)
public/ 웹앱. 빌드 도구 없음
probe/ 갱신주기·변화량 실측 도구. 앱 코드가 아니다
tools/ 암호 해시 생성기·홈 화면 아이콘 생성기 (로컬 전용)
공공데이터포털이 해외 IP를 차단한다. GitHub Actions 러너(미국 애리조나)에서 apis.data.go.kr로의 TCP 443이 거부가 아니라 드롭된다 — connect=0.000000s 후 타임아웃. 같은 러너에서 api.github.com은 0.16초에 응답했으므로 러너 네트워크 문제가 아니다.
서울(icn1)에서는 141ms에 응답한다. vercel.json의 regions를 바꾸면 앱 전체가 죽는다.
"오늘 곡선"을 그리려면 24시간 도는 수집기가 필요하고, 그건 상시 켜진 기기나 유료 인프라를 뜻한다. 둘 다 두지 않기로 했다.
대신 신선도가 좋아졌다. 원천이 60초마다 갱신되는데 요청할 때마다 직접 부르므로 화면은 최대 75초 이내의 값을 보여준다. 수집기를 두던 시절에는 최대 35분이었다.
"지금 차오르는 중"은 앱이 열려 있는 동안 브라우저가 직전 값과 비교해 만든다 (public/app.js의 computeTrend). 서버는 이력을 갖지 않는다.
한국공항공사 전국공항 실시간 주차정보 (공공데이터포털, 데이터셋 15056803)
GET https://apis.data.go.kr/B551178/parking-realtime-status/info
실측으로 확인한 특성:
schAirportCode를 생략하면 1회 호출로 전국 25개 주차장이 모두 온다Vercel 프로젝트에 환경변수 3개가 있어야 한다. 하나라도 없으면 해당 기능이 거부로 끝난다 (통과가 아니다).
| 이름 | 내용 |
|---|---|
KAC_SERVICE_KEY | 공공데이터포털 서비스키 |
APP_PASSWORD_HASH | 접속 암호의 PBKDF2 해시 |
SESSION_SECRET | 세션 쿠키 서명용 난수 |
뒤의 두 개는 이 명령으로 만든다. 암호를 인자로 받지 않으므로 셸 기록에 남지 않는다.
python tools/make_password_hash.py
초기 설정에서 여기 적힌 네 가지 때문에 하루를 썼다. 증상이 전부 "암호가 맞지 않습니다" 하나로 나타나서 원인을 가릴 수 없었다.
환경변수는 저장만으로 반영되지 않는다. 배포를 만드는 순간의 값이 함수에 굳는다. 값을 고쳤으면 반드시 새 배포를 만들어야 하고, 대시보드 Redeploy가 안 걸릴 때가 있으므로 빈 커밋 푸시가 확실하다.
git commit --allow-empty -m "chore: 환경변수 반영을 위한 재배포"
git push origin main
변수는 환경별로 따로 존재한다. Development 에만 넣으면 운영에서는 없는 것과 같다. 목록의 Environments 칸이 세 환경을 모두 덮는지 눈으로 확인한다.
Value 칸에 변수 이름을 넣지 않는다. APP_PASSWORD_HASH 를 값으로 저장하면 17자짜리 문자열이 들어가 검증이 실패하는데, 화면에는 암호가 틀렸다고만 나온다.
순서는 값 먼저, 푸시 나중이다. 푸시를 먼저 하면 옛 값으로 배포가 굳는다.
Vercel → Logs → POST /api/login → Function. 값은 한 글자도 찍히지 않고 형태만 나온다.
login rejected — stored hash fp=b69cd4a7 len=87 algo_ok=True iters=600000
salt=16B key=32B; attempt_len=8 attempt_has_outer_space=False
| 항목 | 읽는 법 |
|---|---|
fp | 서버에 들어간 해시의 지문. tools/check_password.py 출력과 다르면 다른 값이 올라간 것 |
len algo_ok salt key | 해시의 형태. 하나라도 어긋나면 붙여넣기가 깨진 것 |
attempt_len | 브라우저가 보낸 글자 수. 실제 암호 길이와 다르면 입력칸이 오염된 것 |
Vercel에 올리기 전에 이 PC에서 먼저 맞춰본다. 여기서 "일치합니다"가 나온 조합만 올리면 배포 왕복이 사라진다.
python tools/check_password.py
python -m pytest -q # 98개
python probe/check_join.py # 주차장명 조인이 깨지지 않았는지
python tools/make_icons.py # 홈 화면 아이콘 4장 (브랜드 색을 바꿨을 때만)
probe/는 서비스키가 필요하다. probe/.env.example을 probe/.env로 복사해 채운다. .env는 .gitignore로 제외되며, .env.example은 커밋되는 템플릿이므로 실제 키를 넣지 않는다.
인증을 둔다. 다루는 데이터 자체는 공공데이터라 기밀이 아니지만, 불특정 다수에게 서비스를 운영하는 상태를 만들지 않기 위해서다. 암호는 평문으로 저장하지 않고 PBKDF2-HMAC-SHA256 해시만 환경변수에 둔다. 세션은 서버가 서명한 HttpOnly 쿠키이며, 판단은 전부 서버에서 한다.
세션은 1년이고, 쓰는 동안 계속 연장된다. 차 안에서 급히 여는 앱이라 로그인 화면이 뜨는 것 자체가 실패에 가깝다.
한때 5년으로 잡았지만 지켜지지 않는 약속이었다. 크롬·엣지는 쿠키 수명을 400일에서 자르므로 서버가 5년을 적어 보내도 브라우저가 13개월 만에 버렸다. 적은 값과 실제 값이 갈라지면 그 차이는 사용자가 예고 없이 로그아웃될 때에야 드러난다. 그래서 상한 아래인 1년으로 내리고, 짧아진 만큼은 갱신으로 메운다 — /api/parking 은 남은 수명이 절반 아래면 쿠키를 새로 내린다. 앱을 여는 것만으로 만료가 밀려나므로, 1년 넘게 손대지 않았을 때만 한 번 다시 로그인한다.
만료된 세션은 갱신하지 않는다. 기기를 잃었을 때는 SESSION_SECRET 을 바꾸면 발급된 모든 세션이 한 번에 끊긴다.
서버리스라 저장소 없이 시도 횟수를 셀 수 없다. 검증 1회에 드는 시간(반복 60만 회)이 무차별 대입에 대한 유일한 방어선이다.
서비스키는 함수의 환경변수에만 있다. 휴대폰 브라우저가 data.go.kr을 직접 부르지 않으므로 키가 노출되지 않는다.
정적 파일(HTML·CSS·JS)은 인증 없이 받을 수 있다. 내용이 빈 껍데기이고 데이터 API가 막혀 있어 실질적으로 이용할 수 없다.