CORSを使わずに別のサイトのHTMLファイルが読めないか試行錯誤して完全敗北した記録


#Overview

セキュリティ上の観点から、通常別のサイトからのデータは取得できない。
しかし、許可されている別のサイトならデータを取得を可能にするCORSという仕組みがある。
これのおかげでセキュアながら、連携したい部分とは連携できるようになっている。
しかし、全く関係のないサイトにはCORSの設定ができないため、データが取得できないという厳しい制約がある。

いろんなサイトのOGP画像をサーバーを介さずに取得してプレビューを作ろうと考えた。
HTMLファイルの取得で以下のよく見かけるエラーに遭遇。

```terminal
Access to fetch at '<another site url>' from origin 'http://localhost:3000' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.
```

HTMLファイルだけならどうにか取得する方法あるんじゃないか？
そもそもどこかに穴があれば狙われるからまず考えられないが、テキストとして取れないか？という希望があると思わないとどうにかなりそうで:sob:
半分は敗北の未来をわかっていながら、もしかしたら？と浅はかながらもいくつか試したことを残しておく。

結論としてはタイトル通り取得できなかったので、この先に何一つ希望はないということは断っておきたい。


#Target reader

- CORSを使わずに何を試したのか興味のある方。

#Prerequisite

- CORSが何かを理解している方。
- Reactアプリに実装しているため、コードはすべてReactのコードの抜粋になります。
- ブラウザはWindowsのChromeを使いました。


#Body

## Fetch API

CORSのことが完全に頭になかった時に普通に別サイトのHTMLを取得しようとしたコード。
`fetch()`で冒頭に掲載した`Access-Control-Allow-Origin`がないと怒られる。
`XMLHttpRequest`についてはMDNに`fetch()`と同様と読み取れたため試していない。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

```javascript:index.js

const fetchHtml = async (url) => {
  const response = await fetch(url);
  if (!response.ok) {
    const description = `status code:${response.status} , text:${response.statusText}`;
    throw new Error(description);
  }
  return await response.text();
}
```

## iframe

TwitterやGoogle Mapや広告等はiframeで自分のサイトに埋め込むことができる。
実際デベロッパーツールではiframe内のDOMにアクセスできるため、これならいけるかも？とiframeに表示してそこにアクセスできないか試してみた。

```javascript:index.js

const onLoadIframe = () => {
  const iframe = document.querySelector("#inlineFrameExample");
  const iframeDocument = iframe.contentWindow.document;
  const a = iframeDocument.querySelector("a");
  console.log("a:", a);
}
```

`onLoadIframe`を`iframe`の`onload`イベントに接続して実施。
結果としては、HTMLは表示されるものの`iframeDocument`の行で`SecurityError`なるものが発生しアクセス不可。
デベロッパーツールならみれるのにコードからは見れないのか:pensive:

## object

iframeの派生形でobjectタグを使ってテキストとして読み込ませたらどうだろうか？
残念ながら`text/plain`でテキスト指定しても、丁寧にHTMLとして読み込んでiframeと表示は変化なし。
結果としてもiframe同様`SecurityError`なるものが発生しアクセス不可。

```html:index.js
            <object id="inlineFrameExample"
              width="100%"
              height="100%"
              data="https://sample.com/"
              type="text/plain"
              onLoad={handleiFrameonLoad}
            >
            </object>
```

## winow.open()

iframeはダメだったが、`winow.open()`はどうだろうか？
半分駄目だろうなと思っていたが、やることに意味があると思う:laughing:
`winow.open()`ってこんなやつだったなぁ～と懐かしみつつコードを書いてみた。
しかし、情けないことに`addEventListener`や`onload`でFunctionを着火させられなかったため、`window.open()`の5秒後に`querySelector()`を実行するコードになっている:joy:

```javascript:index.js

const openWindow = (url) => {
  const w = window.open("");
  // w.addEventListener("load", (event) => {
  //   const a = w.document.querySelector("a");
  //   console.log("a:", a);
  // })

  // w.onload = (event) => {
  //   const a = w.document.querySelector("a");
  //   console.log("a:", a);
  // }

  w.location = url;

  setTimeout(() => {
    const a = w.document.querySelector("a");
    console.log("a:", a);
  }, 5000)
}
```

結論としては`w.document.querySelector("a")`が動作したところで、iframe同様`SecurityError`なるものが発生しアクセス不可。
これは想定していた…うん…

## Web Worker

色々調べていたらWeb Workerなるものが結構前からいることを知る！
https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API/Using_web_workers
Service Workerしか知らなかったので、ブラウザで以前から複数スレッド使えるのには驚いた。
サーバーサイドでは普通に取得できるし、それを受け取るのも問題ないから、DOMも扱えないバックグラウンドで動くこいつならCORS無視できるのかも？という淡い期待でトライしてみる。

基本的にはこのコードを拝借させていただいた。
https://github.com/facebook/create-react-app/issues/1277#issuecomment-345516463
MyWorkerの部分がエラーになったのでエラー箇所だけ修正した。

```javascript:webWorker.js
export default class WebWorker {
    constructor(worker) {
        let code = worker.toString();
        code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));

        // console.log("code:", code)

        const blob = new Blob([code], { type: "application/javascript" });
        return new Worker(URL.createObjectURL(blob));
    }
}
```

```javascript:myWorker.js
export default function MyWorker(args) {
    onmessage = (e) => {
		const { url } = e.data;
		
		fetch(url)
			.then(response => response.text())
			.then(html => console.log(html));

        postMessage("Response");
    };
}
```

```javascript:index.js
import WebWorker from './utils/webworker';
import MyWorker from './utils/myWorker';

const fetchHtml = async (url) => {
  // Worker initialisation
  const workerInstance = new WebWorker(MyWorker);

  // Communication with worker
  workerInstance.addEventListener("message", e => console.log(e.data), false);
  workerInstance.postMessage({url});
}
```

注意したいのが`myWorker.js`。
`fetch()`の部分は`await`したくなるが、`onmessage`に`async`を付与すると`new Worker`でエラーが発生する。
`webWorker.js`で`code`の出力を見たが、`async`の影響で出力されたコードが結構変わるのでそこが影響しているのかもしれない。

`await`はあきらめて`then()`でつないでいるが、`postMessage("Response")`をHTMLを取得後に出すなら`then()`でつなごう:sweat_smile:

実行結果としては`Fetch API`同様`Access-Control-Allow-Origin`がないエラーだった気がする。


## リダイレクト

視点を変えてCORSのプリフライト後に別サイトにリダイレクトしたらもしかしたら行けないだろうか？と思いつく。
リダイレクトは許可していない旨をMDNで見たような気もするが、この際やれることはやろう。
ソースコードはCloud Functions（サーバーサイド）のコード。
プリプライトは通常通り許可し、本番のGETできたときに302応答でフロントエンドが取得要求しているurlにリダイレクトさせる。

```javascript:index.js

exports.fetchHtml = async (req, res) => {
	res.set('Access-Control-Allow-Origin', '*');

	if (req.method === 'OPTIONS') {
		// Send response to OPTIONS requests
		res.set('Access-Control-Allow-Methods', 'GET,POST');
		res.set('Access-Control-Allow-Headers', 'Content-Type');
		res.set('Access-Control-Max-Age', '-1'); // disable cache
		res.status(204).send('');
	} else {
		res.redirect(302, res.body.url);
	}
};
```

結果としては`Fetch API`と同様で、リダイレクトの効果はなかった。

## WebAssembly

執筆後にWebAssemblyならもしかしてCORSが必要ないとかいうことないだろうか？と思いつく。
簡単に調べてみたところ、BlazorでもCORSが必要とドキュメントにあるためトライせずに終了:expressionless:

https://docs.microsoft.com/ja-jp/aspnet/core/blazor/call-web-api?view=aspnetcore-3.1#cross-origin-resource-sharing-cors

>Blazor WebAssembly サンプル アプリ (BlazorWebAssemblySample) は、呼び出し Web API コンポーネント (Pages/CallWebAPI.razor) で CORS を使用する具体的な方法を示しています。

#Conclusion

比較的簡単に思いつく方法で試してみましたが全てNGで、結局はサーバーで取得してフロントに渡すしかないかなという結論です。
ブラウザで表示はできるし、デベロッパーツールでDOMにもアクセスできるけど、プログラムとしては取得不可…(゜-゜)
仮に方法があったとしても全てのブラウザで動作しないかもしれないし、いずれ塞がれるかもしれないと前途多難。
それでもなお挑戦する方の時間節約になればこれ幸い:joy:

モバイルアプリなら、モバイルアプリでテキスト取得してWebViewにデータ渡せそうな気がする（確証なし）…まあそれだけのためにモバイルアプリは作らないけど

Have a great day!

#References
Create React AppのWeb Workerサポートのissue
https://github.com/facebook/create-react-app/issues/3660