はじめてのリバースプロキシ Cloud Run CORSエラー回避

# リバースプロキシとは

Reactを扱うAIの[v0](https://v0.dev/)にはまり[BlueSkyの画像検索サイト](https://bluesky-media-gallery.vercel.app/)を作っていました。
気に入ったものはボタンを押すとすぐに画像がダウンロードできるという仕様にしたのですがBlueSkyの画像のURLをfetchするとどうにもエラーがでてしまいます。


```js:画像をダウンロードする関数（javascript）
function imagedownload(image_url) {

    fetch(image_url)
    .then(response => response.blob())
    .then(blob => {
        const url = window.URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.style.display = 'none'
        a.href = url
        a.download = "image.jpg"
        document.body.appendChild(a)
        a.click()
        window.URL.revokeObjectURL(url)
    })
    .catch(() => console.error('download error'))
}
```

```:エラー内容
' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```



<br>

例えば以下のURLでこの関数を使うとエラーになります。
https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:2et4xgrlwcqymiyjpvdv4yle/bafkreia7fdlsuaob47fjxy2umdnah6iuiqfy7wew6gg7ampwptwch6ughu@jpeg

<br>

React初心者殺しの**CORS policyエラー**。
私はCORSを理解してませんし、理解できる気がしません。（偉そうにいうことではないですが）

以前[X版](https://x-image-gallery.vercel.app/)を作っていたときは問題なかったのでもうこれはcdn.bskyのせいと決めつけました。

このエラーの解消は諦め、**サーバーを立ててそこ経由で取得ができるんじゃない？**
と調べていたらそれが**リバースプロキシ**でした。

<br>

![reverse-proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/da0a573d-1916-4a14-b10c-5b1f27af24b5.png)

<br>

浅知恵の猿知恵でしたが試したところ上手く行きましたので記念に紹介したいと思います。

:::note info
OSはAlpine Linux（Cloud RunにデプロイするDocker内）
WebサーバアプリはNginxです。
:::

# Nginx Configファイル作成

最初にリバースプロキシを行うためのNginxのConfigファイルを用意しておきます。
なぜNginxかは触ったことがあったからでして、apacheでもできると思います。


設定方法はこちらを参考にしました。

https://qiita.com/schwarz471/items/9b44adfbec006eab60b0

<br>

今回はこのような設定にしました。※ファイル名は拡張子が.confならなんでもよいです

```nginx:proxy_test.conf
server {

    listen       8080;
    server_name  _;

    location / {

    	add_header Access-Control-Allow-Origin "*";
        add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
        add_header Access-Control-Allow-Headers "Origin, Authorization, Accept";
        add_header Access-Control-Allow-Credentials true;
	
    	rewrite /(.*) /$1  break;
    
    	proxy_pass https://cdn.bsky.app/;

    }
}
```

<br>

add_headerはリバースプロキシと関係ありません。（私のやりたかったCORSエラー回避のため追加しています。）関係あるのはrewriteとproxy_passだけです。

rewriteで正規表現を使いマッチしたURLを書き換えることができ、proxy_passは転送先となります。
今回は自分宛（リバースプロキシサーバ、以下代理サーバとします）に来たリクエストを

<br>

<b>"https://（代理サーバのホスト名）/img/～～～.jpeg"
↓
"https://cdn.bsky.app/img/～～～.jpeg"</b>

<br>

と変換することを目的としています。

rewrite /(.*)とすることで「/img/～～～.jpeg」がマッチ。それを$1で受け取ることができ、proxy_passに設定した https://cdn.bsky.app/ につなげて転送できるといった感じです。

今回はホスト名以下をまるごと使うのでこうしましたが、/img/以下だけを使いたい場合は「rewrite /img/(.*) /$1  break;」とすればマッチした部分だけ利用することができます。

意図した通り動くかはこの後のDockerで確認しながら作成した方がよさそうです。
※proxy_passに["/"を含むかどうかで変わるなど](https://qiita.com/master-of-sugar/items/6dc7c03a1f600c1ec24a)、かなり緻密な点があります


# Cloud Run用 Dockerfile作成

この設定ファイルを基にどこかにNginxが動くサーバーが必要となります。
今回はCloud Runを利用することにしました。

なぜCloud Runのメリットは**ドメインの用意が不要**なことです。Compute Engineなどの仮想サーバではドメイン設定をしないとIPむき出しの接続となり、それはそれで問題になるかと思います。

ドメイン不要がどういうことかはこの後見て頂けたらと思います。

Cloud Runを利用するまでは以下を参考にさせて頂きました。

https://qiita.com/massie_g/items/5a9ce514eaa7c460b5e3

<br>

流れとしましてはCloud Buildでコンテナイメージを登録しておき、それを基にCloud Runを作成するような感じとなります。

ということでまずDockerファイルをこしらえます。

```docker:Dockerファイル
FROM nginx:stable-alpine3.20

ADD proxy_test.conf /etc/nginx/conf.d/

RUN rm /etc/nginx/conf.d/default.conf

EXPOSE 8080

CMD ["nginx", "-g", "daemon off;"]
```

<br>

ベースイメージはnginx（タグは無くても大丈夫だと思います。）にして、先ほど作成したconfigファイルを「/etc/nginx/conf.d/」下に置きデフォルトのconfigファイルを削除しています。※ベースイメージのLinuxの種類によって異なる可能性があります

ポートはCloud Runのデフォルトの8080にしておき、Dockerでnginxを動かす際のお約束**CMD ["nginx", "-g", "daemon off;"]** としておきます。

あとはこれをデプロイ。[Cloud Runを使うまで](https://qiita.com/massie_g/items/5a9ce514eaa7c460b5e3)にあるとおり最初にCloud Buildにイメージをビルドします。先ほどのDockerファイルがあるフォルダからpowershellで以下を実行します。


```powershell:powershell
$PROJECT_ID="rational-oasis-XXXXXXXX"  # 自身のプロジェクトID

gcloud config set project $PROJECT_ID

gcloud builds submit --tag gcr.io/$PROJECT_ID/proxy_test  # proxy_testは適当です
```

<br>

あとはCloud Runにデプロイするだけです。このままコマンドでできるようですが、私はコンソールから操作します。
![Snapshot_182.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/4acd7dd3-724c-483d-abd4-45090f776e71.png)


コンテナイメージのURLは<b>gcr.io/(プロジェクト名)/(ビルド名)</b>です。サービス名は適当です。

![Snapshot_183.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/2ffee07f-0419-4426-9de4-40f26d60be9c.png)

![Snapshot_184.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/8f8bbad3-a87a-4369-9b86-0a02b7aeb89e.png)

インスタンスの最小数が0の場合、起動に時間がかかります。ただこれを1以上にすると料金が変わるので注意してください。

![Snapshot_185.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/87e2711e-bfb2-4530-ab04-c63dc3aaf66f.png)

![Snapshot_186.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/7abe0704-e2e1-48ac-ade5-27ffeecdd46a.png)


<br>

成功しましたらエンドポイントURLが出てきます。これが最初に言っていたドメインが不要ということです。

https://cdn.bsky.app を発行された https://proxy-test-1043149056745.asia-northeast1.run.app に置き換えれば同じ画像を表示することができるようになります。

　
https://proxy-test-1043149056745.asia-northeast1.run.app/img/feed_fullsize/plain/did:plc:2et4xgrlwcqymiyjpvdv4yle/bafkreia7fdlsuaob47fjxy2umdnah6iuiqfy7wew6gg7ampwptwch6ughu@jpeg

↓
https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:2et4xgrlwcqymiyjpvdv4yle/bafkreia7fdlsuaob47fjxy2umdnah6iuiqfy7wew6gg7ampwptwch6ughu@jpeg

※わかりづらいですが下の画像を上のURLからも表示できるようになるということです


# おわりに

やっていて **「これってもしや違法なんじゃ…」** と思い **"リバースプロキシ 違法"** で検索すると<b>「漫画村」</b>が出てきました😅

「こんなことできるんだ！」と無邪気に使っていたら違法になるかもしれないので注意してください笑