Node.js Express でPOST本文のRawBodyを取得する

Node.js + Express で、POSTされてきたフォームの本文をそのまま取得したかったのでメモ。

body-parserのverify:rawBodySaverオプションを使う必要がある。

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

let rawBodySaver = function(req, res, buf, encoding) {
  if (buf && buf.length) {
    req.rawBody = buf.toString(encoding || 'utf8');
  }
};

app.use(bodyParser.json({verify: rawBodySaver}));
app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true}));
app.use(bodyParser.raw({verify: rawBodySaver, type: '*/*'}));

app.use(function(req, res){
    console.log(req.rawBody); //rawBody
    res.send(req.rawBody); //rawBody
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
    console.log('App Running...');
});

そのまま生のBody本文使う需要なんかないんだろうな。

JSON変換のサンプルはゴロゴロ見つかったが。

JS Beautifier で JS ファイルを綺麗にする

ターミナルでJSをbeautifyしたかったのでメモ。

インデントが崩れたり難読化されたJSファイルを読みやすくしたい時に使える。

js-beautify – npm

npmでインストール。

sudo npm -g install js-beautify

変換結果が標準出力に出るので別ファイルにリダイレクトする。

js-beautify input.js > output.js 

今までChromeデベロッパーコンソールのpretty-printを使ったり、またはエディタの拡張機能を使ってたんだけどコマンドラインのが便利ね。

Google Spreadsheetの1枚目シートをBigQueryの外部テーブルにする

社内の同テンプレのスプレッドシートまとめるのこれでやると楽だったのでメモ。

#!/bin/bash

SCHEMA=date:DATE,id:STRING,description:STRING
URL=https://docs.google.com/spreadsheets/hoge/fuga
PRJ=myproject
DS=mydataset
TBL=mytable

bq mk --external_table_definition=${SCHEMA}@GOOGLE_SHEETS=${URL} ${PRJ}:${DS}.${TBL}

データセットは先に作っておかないと怒られる。
しかしこの方法はシート1枚目しか対応してない仕様。

複数シートも扱うならAppsScriptでBQのネイティブテーブル直接作るしかない。
外部テーブルじゃないけどさ。

function main() {
  var projectId = 'myproject';
  var datasetId = 'mydataset';

  var app = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = app.getActiveSheet();
  var tableId = sheet.getSheetName();
  
  var table = {
    tableReference: {
      projectId: projectId,
      datasetId: datasetId,
      tableId: tableId
    },
    schema: {
      fields: [
        {name: 'date', type: 'date'},
        {name: 'id', type: 'string'},
        {name: 'description', type: 'string'}
      ]
    }
  };
  try{
    BigQuery.Tables.remove(projectId, datasetId, tableId); 
  } catch(e) {}
  table = BigQuery.Tables.insert(table, projectId, datasetId);
  
  var range = sheet.getDataRange();
  var blob = Utilities.newBlob(convCsv(range)).setContentType('application/octet-stream');
  var job = {
    configuration: {
      load: {
        destinationTable: {
          projectId: projectId,
          datasetId: datasetId,
          tableId: tableId
        },
        skipLeadingRows: 1
      }
    }
  };
  job = BigQuery.Jobs.insert(job, projectId, blob);
}

function convCsv(range) {
  try {
    var data = range.getValues();
    var ret = "";
    if (data.length > 1) {
      var csv = "";
      for (var i = 0; i < data.length; i++) {
        for (var j = 0; j < data[i].length; j++) {
          if (data[i][j].toString().indexOf(",") != -1) {
            data[i][j] = "\"" + data[i][j] + "\"";
          }
        }
        if (i < data.length-1) {
          csv += data[i].join(",") + "\r\n";
        } else {
          csv += data[i];
        }
      }
      ret = csv;
    }
    return ret;
  }
  catch(e) {
    Logger.log(e);
  }
}

GAE Standard環境のNode.jsでリバースプロキシ構築

SSLのリバースプロキシが欲しかったのでメモ

HTTPS -> GAE (myproject.appspot.com) -> HTTP -> GCE (198.51.100.1:8080)

という構成。

npm初期化してhttpとhttp-proxyをインストール

$ npm init
$ npm install http --save
$ npm install http-proxy --save

で、下記のファイルを用意

app.yaml

runtime: nodejs8

index.js

const http = require('http');
const httpProxy = require('http-proxy');
const port = process.env.PORT || 8080;
httpProxy.createProxyServer({target: 'http://198.51.100.1:8080/'}).listen(port);

package.json
package.jsonはnpm initで作るとして、起動時にindex.jsを実行したいので下記のようにscriptsフィールド内でindex.jsを指定する。

  "scripts": {
    "start": "node index.js"
  },

あとはこれをAppEngineにデプロイ。

$ gcloud app deploy

そしてSSLで https://myproject.appspot.com/ にアクセスするとリクエストが http://198.51.100.1:8080/ にフォワードされる。

[Adobe Analytics] Legacy s_code.js から AppMeasurement.js に乗り換える

もろもろ新機能を試したいので、s_code.js から AppMeasurement.js に乗り換えました。

やった事:

ファイル名は s_code.js のままで、 “DO NOT ALTER BELOW THIS LINE” のコメント行配下の本体を、 AppMeasurement.js の本体に貼りかえるというのが基本。

あと、 Media モジュールで動画計測してるので、モジュールを AppMeasurement_Module_Media.js + AppMeasurement_Module_Integrate.js の内容に貼りかえる。

次にプラグインの互換性についてはここで調べる。
https://marketing.adobe.com/resources/help/ja_JP/sc/implement/plugins_support.html
お、 getPageName プラグイン動くじゃーん、と思ってそのまま試したら s.wd がないとか s.fl がないなどエラーが出てきて怒られるので、下記のように一部を変更。

  • s.wd.location を location.href に置き換える
  • s_code.js から s.fl と s.pt を持って来る。

で、出来上がった getPageName プラグインがこれ。

/*
 * Plugin: getPageName v2.1 - for AppMeasurement.js
 */
s.getPageName=new Function("u",""
+"var s=this,v=u?u:''+location.href,x=v.indexOf(':'),y=v.indexOf('/',"
+"x+4),z=v.indexOf('?'),c=s.pathConcatDelim,e=s.pathExcludeDelim,g=s."
+"queryVarsList,d=s.siteID,n=d?d:'',q=z<0?'':v.substring(z+1),p=v.sub"
+"string(y+1,q?z:v.length);z=p.indexOf('#');p=z<0?p:s.fl(p,z);x=e?p.i"
+"ndexOf(e):-1;p=x<0?p:s.fl(p,x);p+=!p||p.charAt(p.length-1)=='/'?s.d"
+"efaultPage:'';y=c?c:'/';while(p){x=p.indexOf('/');x=x<0?p.length:x;"
+"z=s.fl(p,x);if(!s.pt(s.pathExcludeList,',','p_c',z))n+=n?y+z:z;p=p."
+"substring(x+1)}y=c?c:'?';while(g){x=g.indexOf(',');x=x<0?g.length:x"
+";z=s.fl(g,x);z=s.pt(q,'&','p_c',z);if(z){n+=n?y+z:z;y=c?c:'&'}g=g.s"
+"ubstring(x+1)}return n");
s.fl=function(x,l){return x?(''+x).substring(0,l):x};
s.pt=function(x,d,f,a){var s=this,t=x,z=0,y,r;while(t){y=t.indexOf(d);
y=y<0?t.length:y;t=t.substring(0,y);r=s[f](t,a);if(r)return r;
z+=y+d.length;t=x.substring(z,x.length);t=z<x.length?t:''}return ''};

あと、 s.getQueryParam じゃなくて、 s.Util.getQueryParam を使ってね、とヘルプに書いてあるので、 s.Util.getQueryParam を旧 s.getQueryParam にコピー

/*
 * Plugin: getQueryParam
 */
s.getQueryParam = s.Util.getQueryParam;

あちこち実装済みの s.getQueryParam をイチイチ s.Util.getQueryParam に書き換える必要はない。
※/* 20160831追記*/
このままだと第2・第3引数が正常に動作しないので、実はもうちょい工夫が要ります。
さらに、従来のs.getQueryParamのように、複数の引数をカンマ区切りで同時にセットする事も出来ません。
そこらへんも直す必要があります。
getQueryParam versus Util.getQueryParam

s.c_r と s.c_w は いまのところAppMeasurement でも動くので、そのままにしておく。

いまだに Target Classic 使ってるところは s.trackTNT プラグインを使う事もあるだろうけど、これも s.wd が見つからないというエラーが出るので、 s.wd を window に置換すれば良し。

とりあえず、プラグイン類は、 FormAnalysis 以外はちょいちょい弄れば AppMeasurement でも動きそうね。
FormAnalysis は DTM のイベントルール設定で似たような実装をする事も可能だろうけど、面倒だから今のところはスルー。

Google Tag Manager移行後もハードコードのイベントトラッキングを実行する

GTMに移行後、ページに直接onclick記述してるGAのイベントトラッキングが動かないと思ったら、「トラッカー名を設定する」のチェックが必要だった。

[タグを設定] > [高度な設定] > [トラッカー名を設定する] にチェックを入れるだけ。
[トラッカー名]のフィールドは空白のままでOK。

gtm

Facebookのいいねボタンや、Twitterのツイートボタンのクリックを計測する

5年前とはSDKの仕様が変わってたので、改めてSiteCatalystとGoogleAnalyticsを実装してみた。
こちらのページでテストしました。
http://www.kwonline.org/tst/social.php
下記の4つのボタンのクリックを計測してます。

  • Facebook
  • Twitter
  • はてなブックマーク
  • LINE

Facebook
Facebookは以前のall.jsじゃなくて、sdk.jsになってます。
FB.Event.subscribe関数を使う点はsdk.jsでも変わってません。

window.fbAsyncInit = function(){
	//sitecatalyst
	FB.Event.subscribe("edge.create", function(){
		s.linkTrackVars = "events,eVar1";
		s.linkTrackEvents = s.events = "event3";
		s.eVar1 = "facebook";
		s.tl(true, "o", "facebook");
		//google analytics
		ga('send', 'social', 'facebook', 'like', location.href);
	});
};

Twitter
Twitterは通常のボタン設置方法じゃなくて、developerサイトに載ってるscriptに変える必要があります。
https://dev.twitter.com/web/javascript/loading

window.twttr = (function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0],
    t = window.twttr || {};
  if (d.getElementById(id)) return t;
  js = d.createElement(s);
  js.id = id;
  js.src = "https://platform.twitter.com/widgets.js";
  fjs.parentNode.insertBefore(js, fjs);
  t._e = [];
  t.ready = function(f) {
    t._e.push(f);
  };
  return t;
}(document, "script", "twitter-wjs"));

上記のスクリプトに変えた上で、下記のようにclickイベントにバインドします。

twttr.ready(function (twttr) {
	twttr.events.bind("click", function (){
		//sitecatalyst
		s.linkTrackVars = "events,eVar1";
		s.linkTrackEvents = s.events = "event3";
		s.eVar1 = "twitter";
		s.tl(true, "o", "twitter");
		//google analytics
		ga('send', 'social', 'twitter', 'click', location.href);
	});
});

はてなブックマーク & LINE
「はてなブックマーク」と、「LINEに送る」ボタンはコールバック関数がないので、
勝手にclass属性をつけて、JQueryでclickイベントにバインドするという、実装にしました。
もっとスマートなやり方はあるんだろうか。

onclickで実行する関数内から自分自身の要素を取得する

JQueryとか使えば簡単なんだろうけど、なくても出来た。

他人様のサイトなのでHTMLソースをいじれない事情があり、
idもclassもJQueryもない状況でonclickで実行する関数内から自身のhrefを取得したかった。

<a onclick="hoge()" href="http://www.google.co.jp/">test</a>

というAタグがあるとする。

hoge = function() {
  var e = (window.event)? window.event : arguments.callee.caller.arguments[0];
  var self = e.target || e.srcElement;
  console.log(self.href);
};

これでhref属性がコンソールにoutputされる。

参考にしたサイトはこちら。
【javascript】onclickされた時のthis(自分自身)を取得したい。 | げことじ。