フロントエンドJavascriptのみでCloud Storageにファイルをアップロードする
やりたいこと
タイトルの通り、フロントエンドのJavascriptのみを使って、GCP(Google Cloud Platform)のCloud Storageにファイルをアップロードしたい。
下調べ(読み飛ばしても大丈夫だと思います)
Cloud Storageは各種ライブラリが用意されています。しかし、どれもバックエンド、つまりサービス提供者にリソースのアクセス権限を与え、ファイル操作を行うライブラリです。例えば、バケットにオブジェクトをアップロードするライブラリは、こちらで確認できます。ライブラリは、Node.jsやPythonなどありますが、フロントエンドのJavascriptから、直接Cloud Storageにアップロードする方法は紹介されていません。ウェブで調べると、断片的に情報がありますが、認証からアップロードまでを一貫して紹介した記事は見つけられませんでした。そこで、今回の記事では、フロントエンドのJavascriptのみでOAuth2.0でアクセストークンを受け取り、Cloud Storageへファイルをアップロードするまでを行うプログラムを紹介します。
実装方針
Cloud Storageでは、Cloud Storageを操作するためのREST APIが用意されています。今回は、このREST APIを利用します。
認証
OAuth2.0を使います。
API
REST APIにはJSON APIとXML APIが用意されていますが、今回はJSON APIを利用します。
実装
ベースとなる参考記事はGCPの公式リファレンスです[1]。
認証(OAuth2)
このsection「認証」のゴールは、Cloud Storageを操作するために必要なaccess tokenを取得することです。 そのために、Google OAuth2.0を使って認証を行います。認証に必要なソースコードは『OAuth 2.0 for Client-side Web Applications』[2]に記載されています。書くソースコードの量は大したこと無いです(ありがたい…)。
この記事を参考に、実装していきます。
認証のコードを書きたくない場合(とりあえずアクセストークンが欲しい場合)
もしも、認証のコードを書きたくないということであれば、OAuth2.0 Playgroundからアクセストークンを取得することも可能です。OAuth2.0 Playgroundを使ったアクセストークンの具体的な発行方法は、リファレンス[1:1]を参照してください。
認証情報の準備
Cloud Storageを使うために必要な認証情報を取得します。必要な認証情報は
- client id
- redirect URL
- scope
の3つです。
Google Cloud Platformの「APIとサービス」から、「ライブラリ」を選択して、Cloud Storageを追加します。認証情報で、「認証情報を作成」を選択して、OAuth2.0 クライアントIDを作成してください。ここで得られたクライアントIDが必要になります。また、OAuth同意画面も設定してください。scopeの設定はこちらを参考にしてください。
詳しくは、『Cloud Storageの認証』[1:2]をご覧ください。
アクセストークンの取得(ソースコードと動作)
ここでは、アクセストークンを取得するだけのソースコードを書きます。
このソースコードで出来るのは、ユーザーが「Get Access Token」をクリックしたら、Googleの認証画面にジャンプして、操作すれば、Access Tokenを取得できるというものです。
ソースコード
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="/index.js" defer></script>
</head>
<body>
<button onclick="oauthSignIn()">Get Access Token</button>
<form id="formElem">
<input type="file" id="file-upload" name="file-upload" />
<input type="submit" id="submit-button" disabled />
</form>
</body>
</html>
/**
* URLから、access tokenを切り出す
*/
for (value of location.hash.split("&")) {
if (value.indexOf("access_token=") === 0) {
const access_token = value.split("=")[1];
const ele = document.createElement("p");
ele.textContent = "Your access token is '" + access_token + "'.";
document.body.appendChild(ele);
}
}
/**
* ここから下のコードは、ほとんどそのまま
* https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#oauth-2.0-endpoints
* から引用しています。
*
*/
/*
* Create form to request access token from Google's OAuth 2.0 server.
*/
function oauthSignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = "https://accounts.google.com/o/oauth2/v2/auth";
// Create <form> element to submit parameters to OAuth 2.0 endpoint.
var form = document.createElement("form");
form.setAttribute("method", "GET"); // Send as a GET request.
form.setAttribute("action", oauth2Endpoint);
// Parameters to pass to OAuth 2.0 endpoint.
var params = {
client_id: [先程取得したクライアントID],
redirect_uri: [リダイレクトするURL、例えば"http://localhost:3000"],
response_type: "token",
scope: [先程設定したscope、例えば"https://www.googleapis.com/auth/devstorage.read_write"],
include_granted_scopes: "true",
state: "pass-through value",
};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement("input");
input.setAttribute("type", "hidden");
input.setAttribute("name", p);
input.setAttribute("value", params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
動作
以下のように動作します。
Cloud Storageへのアップロード
それでは、前のsectionで得られたaccess tokenを用いて、Cloud Storageへファイルをアップロードします。Cloud Storageではファイルを置く場所を「Bucket」、データ(ファイル)を「Object」と呼びます。
今回は、予め、Bucketは作成されている前提で、以下の記事を書いています。
もしも、Bucketを作成していない方は、『ストレージ バケットの作成』[3]を参考に、作成してください。ちなみにですが、今回の記事の延長線上で、おそらくストレージバケットもAPI叩くだけで作成できてしまうはずです。Cloud Storageのバケット名が必要なので用意してください。
Cloud Storageにファイルをアップロード
では、本題ですが、以下に上で作成したソースコードと組み合わせて、フロントエンドのJavascriptだけで、Cloud Storageのバケットにファイルを送信するソースコードを示します。
ソースコード
htmlは、先程示したのと同じで、Javascriptのコードだけ以下のように書き換えます。
const bucketName = [作成したBucket名];
const clientID = [前のsectionで得たclient ID];
const redirectURL = [前のsectionで設定したredirect URL];
const scope = [前のsectionで設定したscope];
let accessToken = null;
/**
* リダイレクトされた URLから、access tokenを切り出す
*/
for (value of location.hash.split("&")) {
if (value.indexOf("access_token=") === 0) {
const access_token = value.split("=")[1];
const ele = document.createElement("p");
ele.textContent = "Your access token is '" + access_token + "'.";
accessToken = access_token;
document.body.appendChild(ele);
document.getElementById("submit-button").disabled = false;
}
}
/**
* ファイルのアップロード
*/
formElem.onsubmit = async (e) => {
e.preventDefault();
const file = document.getElementById("file-upload");
const filename = file.value.split("\\").slice(-1)[0];
console.log("File Name: " + JSON.stringify(filename));
const extension = filename.split(".").slice(-1)[0].toLocaleLowerCase();
let contentType = null;
// content-typeの判別
if (extension === "png") {
contentType = "image/png";
} else if (extension === "jpg" || extension === "jpeg") {
contentType = "image/jpeg";
} else if (extension === "svg") {
contentType = "image/svg+xml";
} else if (extension === "mpeg") {
contentType = "video/mpeg";
} else if (extension === "webm") {
contentType = "video/webm";
} else {
alert("このファイルは対応していません。");
}
// ファイルの読み込みと、アップロード
if (contentType) {
const reader = new FileReader();
reader.addEventListener("load", async (event) => {
const bytes = event.target.result;
let response = await fetch(
`https://storage.googleapis.com/upload/storage/v1/b/${bucketName}/o?uploadType=media&name=${filename}`,
{
method: "POST",
headers: {
"Content-Type": contentType,
Authorization: `Bearer ${accessToken}`,
},
body: bytes,
}
);
let result = await response.json();
if (result.mediaLink) {
alert(
`Success to upload ${filename}. You can access it to ${result.mediaLink}`
);
} else {
alert(`Failed to upload ${filename}`);
}
});
reader.readAsArrayBuffer(file.files[0]);
}
};
/**
* ここから下のコードは、ほとんどそのまま
* https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#oauth-2.0-endpoints
* から引用しています。
*
*/
/*
* Create form to request access token from Google's OAuth 2.0 server.
*/
function oauthSignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = "https://accounts.google.com/o/oauth2/v2/auth";
// Create <form> element to submit parameters to OAuth 2.0 endpoint.
var form = document.createElement("form");
form.setAttribute("method", "GET"); // Send as a GET request.
form.setAttribute("action", oauth2Endpoint);
// Parameters to pass to OAuth 2.0 endpoint.
var params = {
client_id: clientID,
redirect_uri: redirectURL,
response_type: "token",
scope: scope,
include_granted_scopes: "true",
state: "pass-through value",
};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement("input");
input.setAttribute("type", "hidden");
input.setAttribute("name", p);
input.setAttribute("value", params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
コードは読んでいただければ分かるかと思いますが、以下に、参考記事を紹介します。
- 拡張子とMIMEの対応関係
- curlでCloud Storageにファイルをアップロードする
-
<input type="file">
からBlobを作成
- fetchの使い方
- input fileの使い方
動作
以下のように動きます。画像が小さく、見えにくくて、ごめんなさい。
- アクセストークンを取得したあとの実行結果 *
さいごに
アクセストークンの取得方法が沢山あり、目的にあったものを探すのに苦労しました。
記事に関して不明な点や「ここ、まずいのでは…」というような点がありましたら、コメントまたは akira.kashihara[at]hotmail.com までメールをお願い致します。
-
Cloud Storage の認証 / Google https://cloud.google.com/storage/docs/authentication (2022-03-12閲覧) ↩︎ ↩︎ ↩︎
-
OAuth 2.0 for Client-side Web Applications / Google https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#oauth-2.0-endpoints (2022-03-12閲覧) ↩︎
-
ストレージ バケットの作成 / Google https://cloud.google.com/storage/docs/creating-buckets#storage-create-bucket-console ↩︎
Discussion