정적 객체를 위한 외부 스토리지
GitLab을 구성하여 리포지토리의 정적 객체(아카이브 또는 원시 블롭과 같은)를 콘텐츠 배달 네트워크(CDN)와 같은 외부 스토리지에서 제공합니다.
외부 스토리지 구성하기
정적 객체를 위한 외부 스토리를 구성하려면:
- 왼쪽 사이드바에서 아래쪽으로 Admin을 선택합니다.
- Settings > Repository를 선택합니다.
- External storage for repository static objects를 확장합니다.
- 기본 URL과 임의의 토큰을 입력합니다. 외부 스토리지 설정을 할 때, 이 값을
ORIGIN_HOSTNAME
및STORAGE_TOKEN
으로 설정하는 스크립트를 사용합니다. - Save changes를 선택합니다.
토큰은 외부 스토리지에서 오는 요청을 구분하는 데 필요하므로 사용자가 외부 스토리지를 우회하여 애플리케이션에 직접 접근하는 것을 방지합니다. GitLab은 이 토큰이 외부 스토리지에서 발생하는 요청의 X-Gitlab-External-Storage-Token
헤더에 설정되기를 기대합니다.
개인 정적 객체 제공
GitLab은 개인 프로젝트에 속하는 정적 객체 URL에 대해 사용자별 토큰을 추가하여 외부 스토리지가 사용자를 대신하여 인증할 수 있게 합니다.
외부 스토리지에서 발생하는 요청을 처리할 때, GitLab은 사용자가 요청한 객체에 접근할 수 있는지 확인하기 위해 다음을 확인합니다:
-
token
쿼리 매개변수. -
X-Gitlab-Static-Object-Token
헤더.
요청 흐름 예
다음 예는 사용자, GitLab 및 콘텐츠 배달 네트워크 간의 요청 및 응답의 순서를 보여줍니다:
외부 스토리지 설정하기
이 절차는 외부 스토리지를 위한 Cloudflare Workers를 사용하지만, 다른 CDN 또는 Function as a Service (FaaS) 시스템도 동일한 원칙을 사용하여 작동해야 합니다.
- 아직 선택하지 않았다면 Cloudflare Worker 도메인을 선택합니다.
-
다음 스크립트에서 처음 두 개의 상수에 대해 다음 값을 설정합니다:
-
ORIGIN_HOSTNAME
: GitLab 설치의 호스트 네임. -
STORAGE_TOKEN
: 임의의 안전한 토큰. UNIX 머신에서pwgen -cn1 64
를 실행하여 토큰을 얻을 수 있습니다. 이 토큰을 구성하는 섹션에 설명된 대로 Admin 영역에 저장하세요.const ORIGIN_HOSTNAME = 'gitlab.installation.com' // FIXME: SET CORRECT VALUE const STORAGE_TOKEN = 'very-secure-token' // FIXME: SET CORRECT VALUE const CACHE_PRIVATE_OBJECTS = false const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', 'Access-Control-Allow-Headers': 'X-Csrf-Token, X-Requested-With', } self.addEventListener('fetch', event => event.respondWith(handle(event))) async function handle(event) { try { let response = await verifyAndHandle(event); // 캐시에서 반환된 응답은 변경 불가능하므로, CORS 헤더를 설정하기 위해 다시 생성합니다. response = new Response(response.body, response) response.headers.set('Access-Control-Allow-Origin', '*') return response } catch (e) { return new Response('An error 발생했습니다!', {status: e.statusCode || 500}) } } async function verifyAndHandle(event) { if (!validRequest(event.request)) { return new Response(null, {status: 400}) } if (event.request.method === 'OPTIONS') { return handleOptions(event.request) } return handleRequest(event) } function handleOptions(request) { // 필수 헤더가 유효한지 확인 // 이것이 유효한 사전 비행 요청인지 확인합니다. if ( request.headers.get('Origin') !== null && request.headers.get('Access-Control-Request-Method') !== null && request.headers.get('Access-Control-Request-Headers') !== null ) { // CORS 사전 비행 요청 처리 return new Response(null, { headers: CORS_HEADERS, }) } else { // 표준 OPTIONS 요청 처리 return new Response(null, { headers: { Allow: 'GET, HEAD, OPTIONS', }, }) } } async function handleRequest(event) { let cache = caches.default let url = new URL(event.request.url) let static_object_token = url.searchParams.get('token') let headers = new Headers(event.request.headers) url.host = ORIGIN_HOSTNAME url = normalizeQuery(url) headers.set('X-Gitlab-External-Storage-Token', STORAGE_TOKEN) if (static_object_token !== null) { headers.set('X-Gitlab-Static-Object-Token', static_object_token) } let request = new Request(url, { headers: headers }) let cached_response = await cache.match(request) let is_conditional_header_set = headers.has('If-None-Match') if (cached_response) { return cached_response } // 원래 요청에 설정된 If-None-Match를 덮어쓰고 싶지 않습니다. if (cached_response && !is_conditional_header_set) { headers.set('If-None-Match', cached_response.headers.get('ETag')) } let response = await fetch(request, { headers: headers, redirect: 'manual' }) if (response.status == 304) { if (is_conditional_header_set) { return response } else { return cached_response } } else if (response.ok) { response = new Response(response.body, response) // cache.put는 Set-Cookie 헤더가 있는 응답을 캐시하지 않습니다. response.headers.delete('Set-Cookie') if (CACHE_PRIVATE_OBJECTS) { response.headers.delete('Cache-Control') } event.waitUntil(cache.put(request, response.clone())) } return response } function normalizeQuery(url) { let searchParams = url.searchParams url = new URL(url.toString().split('?')[0]) if (url.pathname.includes('/raw/')) { let inline = searchParams.get('inline') if (inline == 'false' || inline == 'true') { url.searchParams.set('inline', inline) } } else if (url.pathname.includes('/-/archive/')) { let append_sha = searchParams.get('append_sha') let path = searchParams.get('path') if (append_sha == 'false' || append_sha == 'true') { url.searchParams.set('append_sha', append_sha) } if (path) { url.searchParams.set('path', path) } } return url } function validRequest(request) { let url = new URL(request.url) let path = url.pathname if (/^(.+)(\/raw\/|\/-\/archive\/)/.test(path)) { return true } return false }
-
- 이 스크립트를 사용하여 새 워커를 생성합니다.
-
ORIGIN_HOSTNAME
및STORAGE_TOKEN
의 값을 복사합니다.
이 값을 정적 객체를 위한 외부 스토리지 구성하는 데 사용하세요.