Kuzunoha-NEのブログ

プログラミングなどの勉強をしてます

【検索】検索で公式ドキュメントを掘り起こしたいとき

こんばんは葛の葉です。

まぁ私もこんな取るに足らないブログ書いてる私がこんなこと言うのもなんなんですけど、Google検索で調べものをしているときにブログや某IT系記事がちょっと邪魔くさいなって思うときがあるんですよね。モジュールやミドルウェアに何かやってもらおうとするなら、やっぱり公式のドキュメントがとても大事です。

私のブログなんかもそうですけど、結局のところ個人が書いている情報なんていつの情報かわからないですし(その記事の掲載日がそのときの最新の書き方とは限らないため)、内容も途中までのことしか書いてないから実践レベルで必要なことまで書かれていないことがあるんですよね。

やっぱり公式のドキュメントをじっくり読むのがなんだかんだで正解で近道になるような気がするんです。

Nginxのリダイレクト方法について調べる

Webサーバーとしてかなり有名になってきたNginxですが、このNginxにリダイレクトしてもらおうとConfに書く内容を調べようと以下のキーワードで検索をかけました。

nginx redirect

結果は以下の通り

www.google.com

公式のドキュメントが出てくるのは二ページ目!ちょっと前から思ってることなんですけど、これって行き過ぎたSEO対策が原因なんじゃないかなぁって思うんですよね。。。

こういう風にして公式ドキュメントを掘り当てる

nginx document redirect と言った風にdocumentという文言を追加してあげます。

www.google.com

ミドルウェア等は海外製が多く、またその公式ドキュメントには日本語が全くと言っていいほど存在しません。そのため、検索結果で出てきた日本語のページというのは公式のものではないと判断して無視します。時々日本語のドキュメントがあるものもありますけど、翻訳作業が挟むためか内容が少し前のものが出てくることが多いような気がします。

また、公式のURLっぽいドメインかどうか(.comやorgといったメジャーなトップレベルドメイン、そのミドルウェア名がそのまま使われているようなシンプルな独自ドメイン等がだいたい公式っぽい)も見ておきます。

そうやってふるいにかけると以下のようなページが絞られます。

1つめ

nginx.org

3つめ

www.nginx.com

ちょっと大変ですが、Google翻訳を使って翻訳します。これでだいたいやりたいことが理解できます。

書いてて思ったこと

こういう検索方法も大事なノウハウだよなぁと思います。

【TypeScript】おぉ!って思ったJSの書き方

こんばんは葛の葉です。

体調を崩したので今回も省エネです。

JavaScriptを書き始めてはや9ヶ月ぐらいなんですけど、最近、これいいなぁって思うJSの構文を書いていきます。

スプレッド構文

Mozillaのリファレンスはこちら。

developer.mozilla.org

これは配列の要素をバラして展開できる構文のようです。一回、配列の要素をバラすので一度ばらしてから配列に再び挿入することで新しい配列を作成することが出来ます。unshift()push()みたいな破壊的ではない点も良いですし、一度...の意味をわかれば、コード理解もシンプルになりやすいという点もありますね。例えば配列に要素を追加するときに前方に追加しているか、後方に追加しているかというのもわかりやすいですね。

const lists = ["hoge", "piyo", "fuga"];
console.log(...lists); // hoge piyo fuga;

const newData = "moge";
const newLists1 = [...lists, newData]; 
const newLists2 = [newData, ...lists];
console.log(lists); // [ 'hoge', 'piyo', 'fuga' ]
console.log(newLists1); // [ 'hoge', 'piyo', 'fuga', 'moge' ]
console.log(newLists2); // [ 'moge', 'hoge', 'piyo', 'fuga' ]

分割代入

Mozillaのリファレンスはこちら。

developer.mozilla.org

簡単に書くと配列を使って複数の変数を一文で代入出来るものです。先のスプレッド構文を使うことで、配列化することも出来るようですね。

const [x, y, ...somethings] = ["cat", "dog", "pig", "horse", "rabbit"];
console.log(x); // cat
console.log(y); // dog
console.log(somethings); // [ 'pig', 'horse', 'rabiit' ]

字詰め

Mozillaはこちら。2つあるのは字詰めするのが左か右かの違いになります。padはpadding(詰める)の意味だと思います。この構文はEcmaScript2019の構文で比較的新しめのものなので、使えないブラウザもありうる。StringオブジェクトがもつpadXXXXのメソッドに対して、最大の文字数詰める文字を渡してあげればOKというのもわかりやすいです。地味にゼロパディング使うケースがあるのでこういうのはありがたいです。

developer.mozilla.org

developer.mozilla.org

console.log("7".padStart(3, "0")); // 007
console.log("7".padEnd(3, "0")); // 700

【TypeScript】Reactを触っている

こんばんは、葛の葉です。 最近、個人でReactを触っています。

ja.reactjs.org

Reactはもはや説明不要なほど有名なフロントエンド向けのJSフレームワークです。

JSX(TypeScriptならTSX)を使うことが独特。

ReactはJSXという専用の拡張構文を使用します。

ja.reactjs.org

import React from "react";
import * as ReactDom from "react-dom";

const Sidebar  = () => {
  return (
    <div>
        <p>ほげほげ</p>
    </div>
  )
};

ReactDom.render(<Sidebar />, document.getElementById("root"));

Sidebar関数のような関数を作り、さらに関数の返り値にHTMLのようなものを記載します。そのSidebar関数をReactDom.rendorから呼び出します。ちょっと奇妙なのが<関数名 />となっているところです。これがReact独特の記載方法になります。Sidebar関数と便宜的に書いているのですが、実はこれ、Sidebarコンポーネントということになります。サイドバーという画面コンポーネントを作成し、それをReactが描写しているのが上記のコードになります。

UIをきれいに作るために、JSXからトランスパイル時にそのCSS向けに記述し直してくれるCSSフレームワークを使うと簡単できれいなウェブ画面が作成できます。

qiita.com

また、Rectではコンポーネントの持つ状態を管理するReduxという別のパッケージもありましたが、昨今ではReact Hooksという公式で同様な機能を持つようになりました。

react-redux.js.org

Reducerの機能はちょっと複雑ですが頑張って取得していきたいです。

【TypeScript】JSから投げるhttpリクエストのエラーコードHPE_INVALID_CONSTANTを出力

こんばんは葛の葉です。

URLを集めている業務がありまして、httpリクエストを投げてステータスコードを集めているのですが、そのときに時々に発生したエラーコード「HPE_INVALID_CONSTANT」に随分悩まされました。

そのときにこのツイートを見かけてクララが立ちました。

つまり、HttpヘッダーのContent-Lengthの値が実際と違う場合にHPE_INVALID_CONSTANTが発生するようです。

Version

{
  "node": "12.13.1",
  "request": "2.88.2",
  "request-promise": "4.2.5"
}

今回もお世話になっているhttpモジュールとRequest-Promiseモジュールを使います。

nodejs.org

github.com

再現してみよう

以下にソースを書きます。

import http from "http";

import * as rp from "request-promise";

// Initilaize
const port: number = 9991;
const host: string = `http://localhost:${port}/`;

// Create Http Server
const server = http.createServer((_, response) => {
  // Content-Lengthが1byteと宣言する。
  response.setHeader("Content-Length", 1);
  // 実際のデータは3byteでContent-Lengthとは矛盾する。
  response.write("aaa");
  response.end();
});

// Http Server Run
server.listen(port, () => {
  console.log(`listen ${host}`);
});

(async () => {
  try {
    // Http Get Request to Http Server ここでParse Errorが発生しCatchされる。
    const hoge = await rp.get(host);
    // Print Http Response
    console.log(hoge);
  } catch (e) {
    // Print Error Code
    console.log(e.error.code); // HPE_INVALID_CONSTANT
    //console.log(e.error.rawPacket);
    //console.log(Buffer.from(e.error.rawPacket).toString());
  } finally {
    server.close();
  }
})();

const hoge = await rp.get(host);のタイミングでHttpRequestを上記の作成したHttpサーバーに投げています。そのタイミングでParseErrorというエラーが発生します。そのエラーをキャッチした際に表示されるエラーコードがHPE_INVALID_CONSTANTになります。

ブラウザで見るとちゃんと表示されてたりするので、一見すると問題なさそうなページに対し、プログラムからHttpRequestを投げるとHPE_INVALID_CONSTANTというエラーコードが出力されて面食らうというのが調べるに至った経緯というわけになります。

元データはrawPacketへ

キャッチしたエラーにrawPacketが入っている場合があります。バイナリデータになっているのでStringに変換してあげると色々と使えるかもしれないです。

  } catch (e) {
    console.log(Buffer.from(e.error.rawPacket).toString());
  } finally {
    server.close();
  }
HTTP/1.1 200 OK
Content-Length: 1
Date: Fri, 13 Mar 2020 16:47:19 GMT
Connection: close

aaa

ヘッダーに関してはこのままだと使いにくいので、色々と加工してくれるhttp-headersモジュールなんかを使うと良いと思います。

www.npmjs.com

  } catch (e) {
    const packet = Buffer.from(e.error.rawPacket).toString()
    console.log(parse(packet))
{
  version: { major: 1, minor: 1 },
  statusCode: 200,
  statusMessage: 'OK',
  headers: {
    'content-length': '1',
    date: 'Fri, 13 Mar 2020 16:53:31 GMT',
    connection: 'close'
  }
}

Bodyデータは。。。。頑張って加工して使ってみてくれ😥

【TypeScript + AWS】CloudWatchLogsからデータを引っ張りたかったんだ

こんばんは、葛の葉です。

今回はAWSのCloudWatchLogsにたまったログに検索をかけ、それを吐き出すということをやっています。いくつか大変なことがあったので、それを書いていこうと思います。

CloudWatchLogsクラスからインスタンスを作る前に

awssdkを使うにはconfigにユーザー情報を渡してあげる必要があります。ユーザーの情報をいくつか渡す方法にcredentialsファイルを用いた認証方法があります。

https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-files.html

credentialsファイルを用いた認証を行う場合は、awsのGlobalConfigInstanceにSharedIniFileCredentialsクラスを使用する必要があります。

なお、SharedIniFileCredentialsのコンストラクタにはcredentialsファイルに記載したプロファイルを指定することができます。

docs.aws.amazon.com

import * as aws from "aws-sdk";

// CredentialFailsを./aws/から読み込む明示的処理。
aws.config.credentials = new aws.SharedIniFileCredentials({profile: "kuzunoha"});
const clw = new aws.CloudWatchLogs();

credeantialsファイル

[kuzunoha]
aws_access_key_id = ******
aws_secret_access_key = ******

configファイル

[kuzunoha]
region = ap-northeast-1
output = json

大変なこと

aws cliだと勝手にcredentialsファイル読んでくれた気がするのに、同じようにsdkでも読んでくれたらよかったのになーと。credentialsの情報が誤ってんのかとずっと思ってました。はー。

startQuery関数に渡す時間はEpoch時間から引いた時間・・・?

CloudWatchLogsクラス内のstartQueryメソッドの引数となるインターフェイスはStartQueryRequestというものになります。

docs.aws.amazon.com

StartQueryRequest内のstartTimeとendTimeはそれぞれ検索の開始と終わりの日時の情報を渡すのですが、これがUnixタイムを用います。

ja.wikipedia.org

LinuxUnixタイムを用いるにはDateオブジェクトのgetTimeメソッドを用います。

developer.mozilla.org

import * as aws from "aws-sdk";

const logGroupName = "あなたの素敵なロググループ";
const queryString = "あなたの素敵なクエリ";

const startTime = new Date("あなたの検索したい時間").getTime();
const endTime = new Date("あなたの検索したい時間").getTime();

// CredentialFailsを./aws/から読み込む明示的処理。
aws.config.credentials = new aws.SharedIniFileCredentials()
const clw = new aws.CloudWatchLogs();

(async () => {
  const req = await clw.startQuery({ startTime, endTime, logGroupName, queryString }).promise()
  // 処理に続く
})();

大変なこと

getTime関数はミリ秒を含めるけど、startTimeでミリ秒込みで良いのかなんとも書かれていないんですよね。。。。

getQueryResults関数は何度も叩こう。

getQueryResults().promise() はプロミスでWebAPIを叩いているようで、その値はGetQueryResultsResponseという型で受け取れます。

docs.aws.amazon.com

getQueryResultsにはstatusがあり、"Scheduled"|"Running"|"Complete"|"Failed"|"Cancelled"のいずれかがあります。

検索結果をWebAPIで提供しているみたいなので、WebAPIを読みに行くことはすぐ終わるのですが、その検索結果がWebAPIに反映されるのはちょっと時間がかかります。Completeとなれば完了するので、そのステータスになるまで何度か叩いてあげます。

docs.aws.amazon.com

import * as aws from "aws-sdk";

const logGroupName = "あなたの素敵なロググループ";
const queryString = "あなたの素敵なクエリ";

const startTime = new Date("あなたの検索したい時間").getTime();
const endTime = new Date("あなたの検索したい時間").getTime();

// CredentialFailsを./aws/から読み込む明示的処理。
aws.config.credentials = new aws.SharedIniFileCredentials()
const clw = new aws.CloudWatchLogs();

const wait = (ms:number) => {
    return new Promise(resolve => setTimeout(resolve, ms));
};

(async () => {
  const req = await clw.startQuery({ startTime, endTime, logGroupName, queryString }).promise()
  let response: aws.CloudWatchLogs.GetQueryResultsResponse;
  // response.statusがComplete,Failed,Cancelledの何れかになるまで何度か叩いて確認する。
  do {
    await wait(1 * 1000);
    response = await clw.getQueryResults({ queryId: req.queryId }).promise();
  } while (["Scheduled", "Running"].includes(response.status));
  
  console.log(response);
})();

たいへんだったこと

何回かたたかなきゃいけないんだ。。。。

ピンポイントな情報はQiitaやStack Overflowに載ってないこともあるんだ

巨人の肩に乗るってのはこういう大変なことがあるんですねぇ。いや、ずいぶん楽で便利させてもらってますけどね。ドキュメント読んで頑張りましょう〜

【TypeScript】filterで頑張る

こんばんは葛の葉です。

Javascript配列のfilterの使い方を勉強します。Arrayオブジェクト内のメソッドであるfilterを使って条件に満たす要素を取り出し、新たに配列を作成します。

developer.mozilla.org

配列の各要素に対して実行するテスト関数です。この関数が true を返した要素は残され、false を返した要素は取り除かれます。

と書いてあるとおり、コールバック関数内での返却値はBooleanで無いといけません。

// items配列の要素である文字列内に「.html」という文字が含まれているものだけを抽出する。

const items = ["hogehoge", "piyopiyo.html", "fugafuga.css"];

const filtered = items.filter((item)=>{
  return item.includes(".html");
});

console.log(filtered);
// [ 'piyopiyo.html' ]

console.log(items);
// [ 'hogehoge', 'piyopiyo.html', 'fugafuga.css' ] 破壊的ではないので元の配列は壊れない。

includesメソッドは配列か文字列のオブジェクトが持っていて、引数に入れた値と合致する値があるかをBooleanで返してくれます。

developer.mozilla.org

【雑記】自分のサービスを作りたい

こんばんは葛の葉です。

このブログ、自己研鑽として始めることとなってもうそろそろ1年と9ヶ月になるんですね。

そろそろ自分のサービスを作りたいなぁと思っていて、ブログに書き込む時間を減らして、自分の作りたいものに時間を当てたいなぁと思っていたりします。

構成はReactとExpressを使ったSSRなもので、HerokuかFireBaseへデプロイしたいと考えています。デザインにはBulmaを使います。データベースにはMySQLを使います。内容はSNSみたいなもので、具体的なものは決まっていません。出来上がったら公開したいと思っています。どんな形になるかはわからないけれども、とにかく公開までしたいという気持ちがあります。

なんでAWSGCPじゃないかというと開発一人に対してやること多すぎだからです。IAMを開発一人で設定とかやるのとても不毛感ある。

Reactにしたいのは仮想DOM + コンポーネント単位での開発の理解 + ReactNativeもいつか触りたいからです。生のJSでも良いかもとは思ったものの、コードごちゃごちゃにしたくない。

ExpressとSSRにしたのはあんまり利用者側にコストかけさせたくないからです。モバイルに使ってもらいたい気持ちは結構あるので。

MySQLにしたのは、今考えているサービス内容である限りは、リレーショナルな部分が重要になると思っています。

頭で出来ているものがあって、カタチに落とし込めていないので、まずはHtmlとBulmaで1ページを作ろうと思います。

f:id:Kuzunoha-NE:20200222003135p:plain