Brightcove LIVE APIからデータ取得する方法(Lambda@Edge)

# サーバーサイドの実装(Lambda@Edge - node.js)

※注意: GETとPUT(パラメータなし)のみ動作確認しています。
POSTやDELETEはパラメータの書き方が違うはずなので、別途実装する必要があります

```javascript:index.js
/**
 * フロントから直接ブライトコーブAPIに問い合わせると、
 * CORSポリシー違反でデータ取得ができないため実装した
 *
 * 2021.05時点ではGETとPUTのみ動作確認済
 * 
 * フロントからAJAX経由で送られてくるHTTPメソッドとURLを用いて
 * それに応じたブライトコーブAPIのレスポンス(JSON)を返す
 * 例えば、AJAXで次のURLにGETリクエストが届いたら、Lambda@Edge(このindex.js)が発火
 * 
 * https://www.example.com/bc-proxy/redundantgroups/xxxxxxxxxxxxxxxx
 * 
 * bc-proxy以下のURLを切り取ってLambda@Edgeに渡し、ブライトコーブAPIにGETで問い合わせる
 * 
 * https://api.bcovlive.io/v1/redundantgroups/xxxxxxxxxxxxxxxx
 * 
 * 問い合わせ結果のレスポンスを返すことで、CORSポリシー違反になることなくデータが取得できる
 */

const dotenv = require('dotenv').config();
const axiosBase = require('axios');
const axios = axiosBase.create({
  baseURL: process.env.BC_API_PATH_BASE, // e.g. "https://api.bcovlive.io/v1/"
  headers: {
    "X-API-KEY": process.env.BC_API_KEY,
    "content-type": "application/json",
  },
  responseType: 'json'
});

/**
 * 「/bc-proxy/」以下のURLを切り取って返す
 */
function parseApiPath(request)
{
  return request.uri.replace("/bc-proxy/", ""); // e.g. "redundantgroups/xxxxxxxxxxxxxxxx"
}

/**
 * リクエストのHTTPメソッドを返す
 */
function getHttpMethod(request)
{
  return request.method; // e.g. "GET"
}

/**
 * レスポンスをJSON形式で生成する
 */
function responseJson(data = "")
{
  var response = {
    status: '200',
    statusDescription: 'OK',
    headers: {
      'cache-control': [{
        key: 'Cache-Control',
        value: 'max-age=0'
      }],
      'content-type': [{
        key: 'Content-Type',
        value: 'application/json'
      }],
      'content-encoding': [{
        key: 'Content-Encoding',
        value: 'UTF-8'
      }],
    },
    body: JSON.stringify(data),
  };

  return response;
}

/**
 * 開発用のテストイベント
 */
function testEvent()
{
    var obj = {
      "Records": [
        {
          "cf": {
            "request": {
              "method": "GET",
              "querystring": "",
              "uri": "/bc-proxy/redundantgroups/xxxxxxxxxxxxxxxx"
            }
          }
        }
      ]
    };
    return obj;
}

async function get(path, config = {})
{
  var res = await axios.get(path, config)
  .then(json => {
    //console.log('status:', json.status);
    //console.log('body:', json.data);
    return json.data;
  })
  .catch(error => {
    console.log(error.response.status);
    console.log(error.response.data);
    console.log(error.response.headers);
    return false;
  });

  return res;
}

async function put(path, data = {}, config = {})
{
  var res = await axios.put(path, data, config)
  .then(json => {
    //console.log('status:', json.status);
    //console.log('body:', json.data);
    return json.data;
  })
  .catch(error => {
    console.log(error.response.status);
    console.log(error.response.data);
    console.log(error.response.headers);
    return false;
  });

  return res;
}

exports.handler = async(event, context, callback) => {
  // 開発用
  //var event = testEvent();

  var request = event.Records[0].cf.request;

  var apiPath = parseApiPath(request);
  var httpMethod = getHttpMethod(request);

  var res = false;

  if(httpMethod === "GET"){
    res = await get(apiPath);
  }
  else if(httpMethod === "PUT"){
    res = await put(apiPath);
  }

  var response = responseJson(res);

  callback(null, response);
};
```

node.jsを使う場合、axiosとdotenvを使用していますので、
開発環境で以下のような手順でインストールしてから、Lambdaにnode_modulesとしてデプロイする必要があります。

```bash
$ cd /path/to/project
$ npm install axios
$ npm install dotenv
```

.envファイルには次のように記述します。

```bash:.env
BC_API_PATH_BASE="https://api.bcovlive.io/v1/"
BC_API_KEY="xxxxxxxxxxxx"
```

# サーバーサイドの実装(PHP)

私の場合は、予算とか諸々の関係で使えませんでしたが、
EC2とかApacheが使えるなら、以下のようにPHPで実装するのもアリです。

「 `bc-proxy` 」というディレクトリを作って、その中に `index.php` を作成します。
(ディレクトリ名は分かればなんでもいいです)

※注意: GETとPUT(パラメータなし)のみ動作確認しています。
POSTやDELETEはパラメータの書き方が違うはずなので、別途実装する必要があります

```php:bc-proxy/index.php
<?php
ini_set('display_errors', 1);

// CORS enablement and other headers
header("Access-Control-Allow-Origin: *");
header("Content-type: application/json");
header("X-Content-Type-Options: nosniff");
header("X-XSS-Protection: 1");

$currentUrl = $_SERVER["REQUEST_URI"]; // e.g. "/bc-proxy/redundantgroups/xxxxxxxxxxxxxxxxx"
$httpMethod = $_SERVER["REQUEST_METHOD"]; // e.g. "GET"

$apiUrlBase = "https://api.bcovlive.io/v1/";
$apiPath = str_replace("/bc-proxy/", "", $currentUrl);

$url = $apiUrlBase . $apiPath;

$apikey = 'xxxxxxxxxxxxxxxxxx';

// more security checks
$needle = '.io';
$endapi = strpos($url, $needle) + 3;
$nextChar = substr($url, $endapi, 1);

if (strpos($url, 'api.bcovlive.io') == false) {
    exit('{"ERROR":"Only requests to Brightcove Live APIs are accepted by this proxy"}');
}
else if ($nextChar !== '/' && $nextChar !== '?') {
    exit('{"ERROR": "There was a problem with your API request - please check the URL"}');
}

//send the http request
$ch = curl_init($url);
curl_setopt_array($ch, array(
    CURLOPT_CUSTOMREQUEST  => $httpMethod,
    CURLOPT_RETURNTRANSFER => TRUE,
    CURLOPT_SSL_VERIFYPEER => FALSE,
    CURLOPT_HTTPHEADER     => array(
        'Content-type: application/json',
        "X-API-KEY: {$apikey}",
    )
));

$response = curl_exec($ch);
curl_close($ch);

// Check for errors
if ($response === FALSE) {
    echo '{"ERROR": "There was a problem with your API call"}';
    die(curl_error($ch));
}

// return the response to the AJAX caller
$responseDecoded = json_decode($response);

if(! isset($responseDecoded)){
    $response = '{null}';
}

echo $response;
```

PHPで実装する場合は、上のindex.phpと同階層に以下の.htaccessを置きます。
これにより、リクエストURIとHTTPメソッドを用いてブライトコーブAPIに問い合わせができます

```bash:bc-proxy/.htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]
```

# LIVE APIはフロントエンドからデータ取得ができない

https://ja.live.support.brightcove.com/live-api/code-samples/live-api-sample-create-live-job.html

> **Live APIは CORS 対応ではなく**、サーバサイドアプリケーションからアクセスする必要があるため、
> API リクエストは PHP で記述された単純なプロキシを介して送信されます。
> これは、任意のサーバーサイド言語で再現できます。

**ブライトコーブのLIVE APIはCORSに対応していません。**
これが意味することは、jQueryとかを使って、フロントエンドからAJAXでデータ取得しようとしても、
CORSポリシー違反になってデータ取得ができないことを意味します。
(この記事を書いた理由もそこにあります)

つまり、以下のようなコードでAJAXでデータ取得しようとしてもできないということです。

```javascript:script.js
/**
 * Get Data By Ajax
 *
 * @param  string filePath
 * @return Deferred Object
 */
function getData(filePath)
{
  var def = new $.Deferred;

  var options = {
    type : 'GET',
    url : filePath,
    dataType : 'json',
    headers: {
      "X-API-KEY": "xxxxxxxxx",
      "content-type": "application/json",
    },
    cache : false
  };

  $.ajax(options)
  .done(function(data, textStatus, jqXHR){
    console.log('getData done', data);
    def.resolve(data);
  })
  .fail(function(jqXHR, textStatus, errorThrown){
    console.error('getData fail', errorThrown);
    def.reject(errorThrown);
  });

  return def.promise();
}

/**
 * ブライトコーブAPIからジョブステータス情報を取得する(GETメソッド)
 * 
 * @param  string jobId 英数字で構成された文字列
 * @return void
 * @doc https://apis.support.brightcove.com/live-api/references/reference.html#operation/GetLiveJobDetails
 */
function getJobStatus(jobId)
{
  var API_PATH_BASE = "https://api.bcovlive.io/v1/";
  //var API_PATH_BASE = "/bc-proxy/";
  var apiPath = API_PATH_BASE + "jobs/" + jobId;
  
  var res = getData(apiPath);

  res.then(function(data){
    console.log(data);
  });

  res.fail(function(err){
    console.error(err);
  });  
}
```

ただし、Google Chromeに「[CORS Unblock](https://chrome.google.com/webstore/detail/cors-unblock/lfhmikememgdcahcdlaciloancbhjino?hl=ja)」というアドオンを入れるか、
以下のようにセキュリティーエラーを無視するオプションをつけて起動すれば、フロントエンドからもデータ取得ができます

```bash
C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="C://Chrome dev session"
```

でも、その操作をエンドユーザーにまで強要するのは良くないですよね？
なので、以下のような仕組みでライブ配信APIからデータを取得します。

1. CloudFrontの特定のURLにアクセスする (`https://xxxxx.cloudfront.net/bc-proxy/jobs/xxxxxx`)
+ CloudFrontのBehavior設定により、「`bc-proxy`」以下のURLにアクセスしたらLambda関数が起動するようにする
+ Lambda関数でリクエストURLとHTTPメソッドを取得する (GET `/bc-proxy/jobs/xxxxxx`)
+ Lambda関数からブライトコーブLIVE APIに問い合わせ (GET `https://api.bcovlive.io/v1/bc-proxy/jobs/xxxxxx`)
+ JSONオブジェクトが返ってくるので、レスポンスBodyに詰め込んで、CloudFrontのレスポンスに返す
+ 返ってきたJSONオブジェクトをもとにフロントエンドでよしなにする

# フロントエンドの実装

Lambda@EdgeかPHPでJSONデータを取得したら、
以下のようなコードでフロントエンドにオブジェクトを読み込んで、
あとはよしなにします

```javascript:script.js
/**
 * Get Data By Ajax
 *
 * @param  string filePath
 * @return Deferred Object
 */
function getData(filePath)
{
  var def = new $.Deferred;

  var options = {
    type : 'GET',
    url : filePath,
    dataType : 'json',
    //headers: {
    //  "X-API-KEY": "xxxxxxxxx", // <- 記述不要。Lambda側でAPIキーを用いて問い合わせてるのでフロント側から渡す必要はありません
    //  "content-type": "application/json",
    //},
    cache : false
  };

  $.ajax(options)
  .done(function(data, textStatus, jqXHR){
    console.log('getData done', data);
    def.resolve(data);
  })
  .fail(function(jqXHR, textStatus, errorThrown){
    console.error('getData fail', errorThrown);
    def.reject(errorThrown);
  });

  return def.promise();
}

/**
 * ブライトコーブAPIからジョブステータス情報を取得する(GETメソッド)
 * 
 * @param  string jobId 英数字で構成された文字列
 * @return void
 * @doc https://apis.support.brightcove.com/live-api/references/reference.html#operation/GetLiveJobDetails
 */
function getJobStatus(jobId)
{
  //var API_PATH_BASE = "https://api.bcovlive.io/v1/";
  var API_PATH_BASE = "/bc-proxy/";
  var apiPath = API_PATH_BASE + "jobs/" + jobId;
  
  var res = getData(apiPath);

  res.then(function(data){
    console.log(data);
  });

  res.fail(function(err){
    console.error(err);
  });  
}

/**
 * ステータスチェックボタンをクリックしたときのイベント
 */
function eventStatusCheck()
{
  var target = ".btn-status";

  $(target).click(function(){
    var jobId = $(this).data("job-id");
    getJobStatus(jobId);
  });
}

function init()
{
  eventStatusCheck();
}

init();
```