# Static objects external storage

> 原文：[https://docs.gitlab.com/ee/administration/static_objects_external_storage.html](https://docs.gitlab.com/ee/administration/static_objects_external_storage.html)

*   [Configuring](#configuring)
*   [Serving private static objects](#serving-private-static-objects)
*   [Requests flow example](#requests-flow-example)
*   [Set up external storage](#set-up-external-storage)

# Static objects external storage[](#static-objects-external-storage "Permalink")

在 GitLab 12.3 中[引入](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31025) .

可以将 GitLab 配置为从外部存储（例如内容交付网络（CDN））为存储库静态对象（例如，存档或原始 Blob）提供服务.

## Configuring[](#configuring "Permalink")

要为静态对象配置外部存储，请执行以下操作：

1.  导航到" **管理区域">"设置">"存储库"** .
2.  展开" **存储库静态对象"**部分.
3.  输入基本 URL 和任意令牌. [设置外部存储时](#set-up-external-storage) ，您将使用一个脚本，将这些值用作`ORIGIN_HOSTNAME`和`STORAGE_TOKEN` .

需要使用令牌来区分来自外部存储的请求，因此用户无需绕过外部存储就可以直接前往应用程序. 令牌应在来自外部存储的请求中的`X-Gitlab-External-Storage-Token`标头中设置.

## Serving private static objects[](#serving-private-static-objects "Permalink")

GitLab 将为属于私有项目的静态对象 URL 附加一个用户特定的令牌，因此可以代表用户对外部存储进行身份验证. 当处理来自外部存储的请求时，GitLab 将在`token`查询参数或`X-Gitlab-Static-Object-Token`标头中查找`X-Gitlab-Static-Object-Token`以检查用户访问所请求对象的能力.

## Requests flow example[](#requests-flow-example "Permalink")

以下示例显示了用户，GitLab 和 CDN 之间的一系列请求和响应：

sequenceDiagram User->> GitLab：GET /project/-/archive/master.zip GitLab->>用户：302 找到有关 User，GitLab 的注释：位置：https://cdn.com/project/-/archive/master. zip？token =安全用户令牌用户->> CDN：GET /project/-/archive/master.zip?token=安全用户令牌替代对象不在缓存 CDN->> GitLab：GET / project /- /archive/master.zip 关于 CDN，GitLab 的说明：X-Gitlab-External-Storage-Token：secure-cdn-token
X-Gitlab-Static-Object-Token：安全用户令牌 GitLab->> CDN：200 OK CDN->>用户：master.zip 其他对象在缓存 CDN->> GitLab：GET / project /-/ a​​rchive / master.zip 关于 CDN，GitLab 的说明：X-Gitlab-External-Storage-Token：secure-cdn-token
X-Gitlab-Static-Object-Token：安全用户令牌
如果不匹配：etag 值 GitLab->> CDN：304 未修改 CDN->>用户：master.zip 结束

## Set up external storage[](#set-up-external-storage "Permalink")

尽管此过程使用[CloudFlare Workers](https://workers.cloudflare.com)进行外部存储，但其他 CDN 或功能即服务（FaaS）系统应使用相同的原理工作.

1.  如果还没有，请选择一个 CloudFlare Worker 域.
2.  在以下脚本中，为前两个常量设置以下值：

    *   `ORIGIN_HOSTNAME` ：GitLab 安装的主机名.
    *   `STORAGE_TOKEN` ：任何任意的安全令牌（例如，您可以通过在 UNIX 计算机上运行`pwgen -cn1 64`来获得一个）. 按照[配置](#configuring)部分中的说明将此令牌保存到管理面板.

        ```
        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);

            // responses returned from cache are immutable, so we recreate them
            // to set CORS headers
            response = new Response(response.body, response)
            response.headers.set('Access-Control-Allow-Origin', '*')

            return response
          } catch (e) {
            return new Response('An error occurred!', {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) {
          // Make sure the necessary headers are present
          // for this to be a valid pre-flight request
          if (
            request.headers.get('Origin') !== null &&
            request.headers.get('Access-Control-Request-Method') !== null &&
            request.headers.get('Access-Control-Request-Headers') !== null
          ) {
            // Handle CORS pre-flight request
            return new Response(null, {
              headers: CORS_HEADERS,
            })
          } else {
            // Handle standard OPTIONS request
            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
          }

          // We don't want to override If-None-Match that is set on the original request
          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 will never cache any response with a Set-Cookie header
            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
        } 
        ```

3.  使用此脚本创建一个新的工作程序.
4.  复制`ORIGIN_HOSTNAME`和`STORAGE_TOKEN`值. 使用这些值为[静态对象配置外部存储](#configuring) .