gRPCでのエラーに関してgolangではどう向き合うべきなのか知るべくひとまず知識をまとめた。
golangではerrorをどのように取扱うのがよりよいのかというのがしばしば話題になることがある。
golang自体がシンプルなゆえに、考慮することが多い。
標準パッケージだけでは難しいことも多いため、errorsなども利用して行うことが多い。(Error handling in Upspin)
自分はgolangを使ったmicro serviceの開発をしていることもあり、grpcでのエラーに関してどう向き合うべきなのか知る
べく知識をまとめた。
サーバー、クライアントでのerror handlingの例としてはavinassh/grpc-errorsが参考になる。
protoを利用してる前提なのでerror modelも参考になる。
gRPCのエラーはGoogle APIのエラーモデルの設計に基づきエラーコード、ステータス、詳細情報が返される。
google.rpc.Status
1 | package google.rpc; |
status codeはgoogle/rpc/code.protoにあるとおりで、このステータスはHTTP Status Codeとの整合性を保つことができるようになっている。
https://cloud.google.com/apis/design/errors#handling_errors
こうすることでクライアントとはhttpで、micro serviceとはgrpcでメッセージングしているサービスの場合においても
整合性を保ったままエラーの設計ができる。
1 | import ( |
またgrpcサーバーの実装としてはgoogleapis/rpc/errdetailsを使うことでエラー詳細を含めたエラーを生成することが容易にできる。
1 | import ( |
またこの詳細メッセージはclient側ではstatus.Details
を使うことで取り出すことができる。
1 | for _, detail := range st.Details() { |
とてもシンプルなclientの利用事例は以下のようになる。
1 | package main |
ますは前回と同じくcallstackを見てみる。
1 | // net/http/client.go |
Client構造体のコードコメントを見ると以下のようにあり
1 | Transport specifies the mechanism by which individual |
どのメカニズムでtransportするのかはTransport
の実装できまる。
nilの場合はデフォルトでHTTPが使われる。つまりhttp以外で会話するclientを作成する際も
このtransportをサポートしていれば良いってこと。
実際はhttp clientのtransportがRoundTripper
なので、別のプロトコルの場合はこのRoundTripperを実装することになるのかな。
話は少しそれるがTransport構造体もざっとみてみると
1 | type RoundTripper interface { |
RoundTripper
のiterfaceからRoundTrip関数を持ち、実際にこのRoundTripでサーバーとの会話を行う。
続いて実際にRequestを行っている処理を見てみる。
1 | func (c *Client) Get(url string) (resp *Response, err error) { |
ということで、最終的にRoundTripper
のRoundTrip
をcallすることになる。
これからclientのmiddlewearはRoundTripを実装するRoundTripperであれば良い。
ちなみにTransportのRoundtripの実装を見ると
1 | // roundTrip implements a RoundTripper over HTTP. |
optionのroundTripperであるpconn.alt
がある場合はhttp2などが利用される。
またgetConn
をみると既にidleなconnectionがある場合はそちらを利用する設計なのがわかる。
1 | // getConn dials and creates a new persistConn to the target as |
ここまでの結果から実際の具体的な実装をしていく。
middlewearはhttp.RoundTripper
をオプションにとりhttp.RoundTripper
を返す関数であればよい。
またhttp.Serrverのmiddlewearを実装下のrと同じく、clientのmiddlewear関数もhttp.Requestを受け、http.Responseを返す関数をオプションとする関数を返す必要がある。
1 | func adapter(next http.RoundTripper) http.RoundTripper { |
またこれらの関数をClient構造体のTransportメンバーに渡せるようにする関数を作成する必要がある。
1 | func useAdapters(adapters ...adapter) adapter { |
のようにすることでhttp.clientのmiddlewearでmiddlewearを利用できるようになる。
この実装を見ても、serverのmiddlewearと同じ実装出ることがわかる。
実際のコードは以下
1 | package main |
golangのhttp server, clientともに同じ設計でmiddleweaerの実装があ可能な設計が取られており、
またclientはtransporterの実装によりプロトコルを変更・拡張することができる。
柔軟な設計はgolangでアプリケーションを作成するにあたっても良い手本となりそう。
今回はHTTPサーバーおよびHTTPクライアントを読んでみた。
結果として、標準パッケージのみで比較的容易に実装できる。
これはgolangの柔軟な言語仕様なんだろう。
golangらしいコーディングの勉強にもなりました。
およそ一般的なechoサーバーの実装は以下のような感じと思われる
1 | package main |
callstackを見ていくと
1 | func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { |
patternに適合したhandlerを登録する。DefaultServeMuxで初期設定で提供されている。
ちなみにServeMuxはhttpマルチプレクサになる。
また、コードコメントに以下のようにある
1 | Longer patterns take precedence over shorter ones, so that |
パスのパターンマッチはより長いパターンのほうが短いパターンより優先されるとのこと。
さらにhandler optionは
1 | handler func(ResponseWriter, *Request) |
ここからわかるようにhandlerはrequest writerとresponseの参照をオプションとする関数である。
これを満たせばよい。
1 | func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { |
HandleFuncはServeMuxのHandleをcallすることになる。
1 | type ServeMux struct { |
pathにマッチしているhandlerを登録している。
次はこのhandlerを呼び出すところ。
1 | // net/http/server.go |
最終的にhandlerはServeHTTP
をcallする形になる。
そしてこのhanderは以下のinterfaceを持っている。
1 | type Handler interface { |
これから作成するmiddlewearはこのインターフェイスを満たす関数である必要がある。
1 | Except for reading the body, handlers should not modify the provided Request. |
middlewear自体の内部実装の注意点としては、
公式のコメントにある通りでRequestBodyをあくまでReadするだけで変更してはならない。
またServerにはHandle
とHandleFunc
が実装されており、HandleFuncをつかうことでハンドラ関数を登録できることわかる。
1 | func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } |
このHandleFuncのServerHTTP
は以下のような実装である
1 | type HandlerFunc func(ResponseWriter, *Request) |
HandlerFuncをcallする関数になっている。
ここまで見てきた通りmiddlewearはhandlerで実装されるように設計していくことで、あとは既存の機構で適宜処理される。
またmiddlewerはアプリケーションの処理の前後にmiddlewearの処理挟むため、イメージ的に
[request] –> [middlewear] –> [another middlewear] –>
… –> [final] –> [middlewear] –> [another middlewear] –> … –> [response]
という動作が期待される
具体的には以下のような感じになってほしいものである。
1 | http.Handle("/", middleware1(middleware2(... (finalHandler) ...)) |
引数として受けた次のmiddlewareを呼び出す前後に何らかの操作を実行し。
新しいmiddleware1を返す関数でmiddlewearを実装する必要がある。
各middleware関数は実装としては以下の形であればよい
1 | func middleware(next http.HandlerFunc) http.HandlerFunc { |
HandlerFuncをオプションにとり、HandlerFuncを返す関数である。
http.HandlerFuncはinterfaceからServeHTTP
を実装しているので最終的にそれをcallする。
あとはmiddlewearを複数登録できるようにラッパー関数を作成していく。
ということで実装してみる。
1 | // add type for middlewear |
上記のようにmniddlewear
というタイプを作成し、middlewearを登録するラッパー関数を作成する。
今回は最終的にリクエストを処理する関数を別途登録するデザインとなっている。
またリスト内のfinal handlerから順番に登録した順番と逆順で直前の関数に渡すようにすることで、
1 | http.Handle("/", middleware1(middleware2(... (finalHandler) ...)) |
このパターンを実現できる。
最終的に以下のようになった。
1 | package main |
1 | $ go run main.go |
ということで期待通りの実装ができた。
1 | type Handler interface { |
http.Handlerが以下のinterfaceに依存しており、
ServeHTTPの実装がhandlerを呼ぶ形になっている。
この実装のため非常に効果的にmiddlewearを作成することができる。
このあたりがgolangは柔軟に設計されていて,interfaceにのみ依存したkたちで
実際の実装をしていく感じを体験できるものだと思う。
次はhttp.Clientでも同じように見ていければ。
TypescriptCompiler APIを利用することでASTの解析および変更が可能。
独自の型解析機構や構文生成機構を作成できる。例えば、TSからJSへの変更の際に独自に必要な負荷情報を追加・編集してコンパイルすることが可能になる。
ちなみにASTの情報はAST Exploerで確認ができるため、開発の際にはこれを参考にする。
簡単なtransformコンパイラを作成して見ていく。
まずはtypescriptのconfig解析機構。
1 | import * as ts from 'typescript'; |
getParsedCommandLineOfConfigFile
を使うことでファイル名を指定してconfigファイルをreadできる。
解析結果のオブジェクトをもとにプログラムを作成し、コンパイルを行う
コンパイル関数
1 | // typescript compile option |
createProgram
でProgramというコンパイル単位のインスタンスを生成する。
Programインスタンスはコンパイルに必要な設定情報やソースファイル情報などをもっている。
Programのemit()
によりjsファイル、宣言ファイルを発行する。
ここでtargetSourceFileがない場合は、すべての対ファイルに対してのjsファイルと宣言ファイルが発行される。
emmit()のcustomTransformers
オプションには、独自のtransform関数をフックできるようになっている。
それぞれ、jsファイル発行前後、宣言ファイル発行後のイベントフックできる。
各ステップでのコンパイルの結果はDiagnostic
という共通のInterfaceをもっており、
programインスタンスから収集が可能になっている。
コンパイル結果のjsコード結果を取得する場合はcreatePrinter()
を使い、printerインスタンスを使う。
今度はコードのtransformを行うcustom transform関数をcustomTransformers
に追加する。
custom transformにはtypescriptのASTが与えられる。
またvisitorというAST解析、コード修正を再帰的
に呼び出す必要がある。
簡単なtransformerを作成する。
1 | function customTransform(options: customTransformOption): ts.TransformerFactory<ts.SourceFile> { |
transformerはNode変更に必要な関数TransformerFactory
を生成する必要がある。
visitNodeで所定のNodeに対してtransnformを適用する(ここではtransformer
)。
transformerの処理は以下のようにする。
1 | function transformer(context: ts.TransformationContext, sorce: ts.SourceFile, options: customTransformOption): ts.Visitor { |
今回は関数宣言を変数宣言に置き換える簡単なものを作成する。createVariableDeclarationList
を作成して新たなNodeを生成しているが、updateVariableDeclarationList
などもあり、既存のNodeに追加することも可能である。
基本的にcreate
とupdate
はそれぞれサポートされているのでオリジナルのNodeを更新することも可能。
webpackやrollupなどのツールの場合、独自のtransformerの設定がサポートされているので、自前のビルド環境に組み込むことは簡単。
1 | // webpack.config.js |
1 | // rollup.config.js |
このtransformerを実際に実行するためにいくつか変更をする。
1 | - const WriteFileCallback = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {} |
1 | const customTransformers: ts.CustomTransformers = { |
1 | + compile(file, CJS_CONFIG); |
この変更を加えて次のファイルのcompileを実行する。
1 | // example.ts |
1 | $ npx ts-node custom-ast-transform.ts example.ts |
transformされた結果としてのコードを得ることができる。
1 | // example.js |
コールスタックをみていくと、program#emitでprogram#runWithCancellationToken
が呼ばれ、このrunWithCancellationTokenは処理をprogram#emitWorker
に移譲する。
この内部でcreateTypeCheckerによりchecker
オブジェクトが渡される。これにより多くのAPIが提供される。その一つのTypeChecker#getEmitResolverによりemitresolverが提供される。
1 | program#emit |
引き続きemitWorker内でemitFilesが呼ばれ、オプションとしてsourceFileとemitResolverが渡される。
emitFilesではtsをjsにtransformしていく。
forEachEmittedFileのオプションで渡されるemitSourceFileOrBundleのemitJsFileOrBundleでtransformNodesが呼ばれ変換される。
transformationで変換する。
1 | emitter#emitFiles |
最後にprintSourceFileOrBundleが実施されてファイルに出力される。
今回は調査のために簡単なサンプルを作成しただけになるが、Typescript Compiler APIを活用することで独自の型チェック機構(linter)のようなものや型解析からのドキュメント自動生成、Debuger、polyfillの自動追加機構などのプラグインを作成可能になる。
APIの変更履歴はAPI Breaking Changesにあるので、利用するTypescriptのバージョンによっては確認しておくと良さそう。
型推論自体はinferTypesで行われ、このメソッドはcreateTypeChecker
で生成されるchecker
を経由して実行されている。initializeTypeChecker
で
1 | program#getTypeChecker |
型推論に関しては別途で見てみよう。
Learning GraphQL を読むんで再認識したことも中心にメモした。
GraphQLはこのグラフ理論に基づく考え方を知っておく必要がある。
ノード(節点・頂点)の集合とエッジ(枝・辺)の集合で構成されるグラフに関する数学の理論。
1 | G(グラフ) = (V(頂点)、E(頂点)) |
で表すことができる。
ノード間に方向や階層がないものを無向グラフ
という。
ノード間の移動はどこからでもできて、方向は任意。非線形データ構造。
また、辺が順序付けられたペアであるときは有向グラフ
になる
Facebookの各ユーザーの結びつきは、相互に関連し合う多数の関係を持つ構造で無向グラフ。
それに対してTwitterは有向グラフ。フォローするが、フォローされるわけではないため。
GraphQLはクエリ言語および実行エンジンであり、クエリを使用してデータを変更または削除できる。
GraphQLクエリをAPIに送信し、1つ以上のデータベース、REST API、WebSocketなどにデータを格納する。
MySQLのINSERT、UPDATE、DELETEと違いデータ変更を1つのMutation型にまとめ、データ型にまとめ実行するなどが可能。
ソケットを介しデータ変更を監視するためのsubscribe型がある。
Graphqlの操作タイプは3種類ある。
GraphQLのqueryの例
1 | query { |
上記は以下のようなhttpリクエストを通じて実行できる。
1 | $ curl 'http://my.graphql.com' |
データ更新する場合は次の雰囲気のクエリを実行する。
1 | mutation { |
こちらは以下のhttpリクエストで行える
1 | curl 'http://my.graphql.com' |
フィールドには引数を受け付けることもできる
再利用可能なクエリの実行単位。
大きなクエリや頻繁に利用されるデータ要件を分割管理することができる。
1 | fragment somethingFields on Person { |
ざっくりGraphQLがどのようなクエリやフィールドをサポートしているのかを問合る機能。
Schema、Type、TypeKind、Field、nputValue、EnumValue、Directive
アンダースコア()で始まるこれらはすべてイントロスペクションを表す。
内部の情報をクエリ経由で参照できる。つまりそのgraphqlサービスが提供するすべてを知ることができる。
1 | { |
1 | { |
Directiveを利用することで既存の型システムに注釈をつけることができる。
ただしこればGraphqlサーバーの実装による。
https://graphql.org/learn/queries/#directives
1 | directive @deprecated( |
上記の場合@deprecatedディレクティブ宣言が与えられると、引数types(reason: String)とlocations(FIELD_DEFINITION | ENUM_VALUE)を適用するかどうかはサーバーの実装次第になる。
@exampleディレクティ部はフィールドFIELD_DEFINITION
と引数定義ARGUMENT_DEFINITION
に注釈をつけることができる。
クエリの実行をスキップする。
1 | directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT |
$isStatusがtrueの場合のみクエリが実行される。
@skipとは逆にクエリの条件にふくめることになる
1 | directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT |
GraphQLはデータを無限に再帰検索しない。
Graphデータが無限再起でないことを保証する方法がない。
また再帰的にしても、クエリを利用するアプリケーションの制約があるため利用できないことがある。
対策法として、任意の深さまでデータを取得し、更に追加の情報を取得する必要がある場合は「もっと見る」などのリンクを追加することがよい。
IDがintなのかstringがいいのかとかの話につながるが、Scaler-IDを読むと以下の通り。
The ID type is serialized in the same way as a String; however, it is not intended to be human‐readable. While it is often numeric, it should always serialize as a String.
IDはStringと同じ方法でシリアライズされます。ただし、人間が読める形式ではありません。
多くの場合数値ですが、常にStringとしてシリアル化する必要がある。
graphql.jsを例にすると、lexer, ast, parserがあることから、これを基準に各言語でも言語処理が可能。
GraphQLは既にFaceBookを離れてGraphQL Foundation
となっている。
Apollo GraphQLでGraphQL界隈のツールを多く提供している。Meteor Development Group Inc.
が母体。
REST APIでは、複数のエンドポイントにアクセスしてデータを収集する。
GraphQLはデータ要件を含む単一のクエリをGraphQLサーバーに送信するだけになる。
よって、複数のエンドポイントを束ねたエンドポイントを構築するよりも、ミドルウェアとしてGraphQLを導入する。
また、スキーマファーストでの開発となる。既にあるリソースを用い、データ構造を抽象化していくことになる。
あらかじめ各データのスキーマを定義しておくことでデータ結合のためのビジネスロジックの開発から解放される。
Falcorのような解決方法もあるがGraphQL
のエコシステムを選ぶことが懸命になっている。
実行クライアントを制限。認証Tokenなどを使い実行クライアントを認証することができる。
(Githubなど)
Slowクエリなどの問題は適切なタイムアウトを設定しておく必要がある。
ただし、jmutationなどが実行された場合、すでにデータに不整合が発生していることもある。
クエリの深度を限定する。再帰的に検索することにもなるため、クエリ実行時にAST分析を行い、最大深度以上のフィールをを無視する。(またはエラーにする)実装が必要。
DoSなどでネスとクエリで攻撃をかけることも可能。
複雑なクエリも定義できるが、実装は難しくなることもある。
また、複雑ゆえに本来隠匿されるべきデータへのアクセスにより、情報が漏洩することや、
データ構造からSQL/NoSQLへのInjectionも起こりえそうなため
できるだけクエリはシンプルに保つことが望ましそう。
アカウント認証、パスワード認証などの検証機構は提供されてない(あくまでクエリ言語なのため)。
実装するやり方にもよるが、自前でタイプなどの実装が必要になることも。
Validation不備の場合、webのアプリケーションと同じく、injectionやXSSの脆弱性を生むことにもつながる。
そもそもGraphQLで実現すべき機能なのかも踏まえて検討するべき。
Apollo Server利用にしている場合。SchemaDirectiveVisitor
を利用する。
このクラスにはllocationsに対応するメソッドが準備されているのでそれをオーバーライドすることになる。
SchemaDirectiveVisitor
を継承したDeprecatedDirective
を作成。
1 | // directive @deprecated( |
1 | import { makeExecutableSchema } from 'graphql-tools'; |
ユースケーストして日付フォーマットの変更の場合
1 | import { defaultFieldResolver } from "graphql"; |
名前の通り。
https://github.com/apollographql/apollo-client
アプリのデータと状態を管理するClient。
Apollo Clientのキャッシュ機構。
Apollo ClientのDataProxyを通して、readQuery
, readFragment
, writeQuery
, writeFragment
インターフェイスを経由してcacheとやり取りする。
query
の場合は、サーバーにリクエストするが、readQuery
はApplicationにcacheあがない場合はエラーを投げる。
1 | const { todo } = client.readQuery({ |
1 | client.writeFragment({ |
GraphQLサーバーへのリクエストを制御するインターフェイス。
GraphQLリクエストを実行すると、各Linkの機能が次々に適用されます。
Apollo Clientコアにある機能をモジュラーリンクに移行する予定。
1 | ApolloLink.from([...]) |
from
で配列を受け取りそれらを単一のリンクに統合する。
内部的にreduceする。
1 | ApolloLink.concat(x, y) |
concat
でリンクを結合して1つにする。
状況によってことなるリンクを使うのであれば
1 | ApolloLink.spilit(conditon, condition_is_true, condition_is_false) |
spilit
のオプションは第一引数に条件、第二引数にtrueの場合のリンク、第三引数にfalseの場合のリンク
ローカルの状態を管理する。
起動時にwithClientState
でローカルのデータストアに状態を登録する。
clientの永続化ストレージにstateを自動的に保存または復元する。
1 | import { InMemoryCache } from 'apollo-cache-inmemory'; |
GraphQLまたはネットワークエラー発生時のロジックを提供する。
以下のオブジェクトキーが利用できる。
operation | エラーとなった操作 |
response | 次のリンクチェーンに渡されるレスポンス |
graphQLErrors | GraphQLエンドポイントからのエラー配列 |
networkError | errorsGraphQLサーバーのレスポンスエラーまたは結果のパースエラー |
forward | チェーン内の次のリンクへの参照。return forward(operation)コールバックを呼び出すとリクエストが再試行される |
名前の通り。
https://github.com/apollographql/apollo-server
JavaScript GraphQLサーバー。schmemeとresolverを定義。
バックエンドに既存のREST API、Database, Microserviceの場合、それぞれが持っているミドルウェアと一緒に組み込むことが可能。
またAmazon LambdaやMicrosoft Azure Functionsなどのサーバーレス(Faas)をバックエンドに使うことも可能。
apllo-serverのintegrationには以下がある。
フロントエンドの開発者がバックエンドの実装を待つことなくUIコンポーネントと機能を構築できるようにするためにmocking機能がある。
モックの例
1 | const { ApolloServer, gql } = require('apollo-server'); |
モックの作成を自動化するためにMockList
クラスを利用できる。
1 | const { MockList } = require('apollo-server'); |
リゾルバーはGraphQL操作をデータに変換するための方法を提供します。
すべてのクエリに対応するために、スキーマはすべてのフィールドに対して解決関数を持っている。
この関数の集合をResolver Map
という。
スキーマのフィールドと型を関数で紐付ける。
1 | const { gql } = require('apollo-server'); |
型とリゾルバは組み合わせることができるので、複数に分けて管理することは可能。
schemeで定義されたフィールを返すPromise関数。
parent | 親引数。親フィールドでリゾルバから返された結果を含むオブジェクト。最上位の場合はrootValue から渡された値。 |
args | クエリ引数。クエリ関数に渡される引数。 |
context | クエリ、リゾルバで共有されるオブジェクト。認証スコープ、データベース接続、カスタムフェッチなど複数のリゾルバで共有されるオブジェクト。 |
info | フィールド名、ルートからのパス、クエリの実行状態などが含まれる。参照 |
contextの例
1 | const server = new ApolloServer({ |
ReactのためのApolloClient拡張。
GraphQLサーバーからデータを取得し、ReactのComponentにprops経由で接続する。
よく見られる高階関数パターン。
1 | import ApolloClient from 'apollo-boost'; |
でコンポーネントにclientを渡すことができる
https://github.com/apollographql/react-apollo/blob/master/src/ApolloProvider.tsx#L9
各コンポーネントからクエリを発行する。
複数のクエリをcomposeして発行することも可能。
ということでここまでで時間切れ。
v11.7.0でworker_threads
が取り込まれたことで、スレッド処理が可能になった。
https://github.com/nodejs/node/pull/25361
ちなみにv11.7.0 以前では worker_threads
の利用の際には--experimental-worker
オプション付きで利用できていた。
IOバウンドな処理を考慮した場合、処理の実行タイミングを変更することで、回避することができる。
一般的なEventloopを利用したバックグラウンド処理がそれにあたる。
負荷の高いコードを分割して、setImmediate
を活用し、IOの後で評価することができる。
1 | ; |
CPUバウンドな処理が渡されるとJavaScriptはシングルスレッドなので、WebアプリケーションだとUIがフリーズし、他の処理がキューイングされる。
そこで、CPUバウンドな処理の場合はマルチプロセス機構を使い処理することも可能。
マルチプロセスなので子プロセスは独自のメモリを持っているが、プロセス間に共有メモリはない。
また、プロセス間のメッセージングはIPC
となる。
起動時にモジュール、を割り当て(その他のオプションも)、プロセスを起動する。
起動時のメモリ割り当てが高価な点やリアルタイム性から利用できるシーンが決まってくる。
複数プロセス利用はjest-workerなどで利用されているnode-worker-farm
のモジュール利用することが現在のところ良さそう。
1 | // index.js |
1 | // child.js |
child_processと同じく、子プロセスをforkする。
child_processでは実行モジュールなどをオプションとして渡す必要があるが、
clusterは親プロセスと同じモジュールの先頭から実行するため不要になる。
Nodejsのアプリケーション自体を並列化する。clusterモジュール
が利用できる。
マスタープロセスにリクエストが来ると、ワーカープロセスに処理を委譲する。
1 | const cluster = require('cluster'); |
マルチプロセスを利用した場合は、プロセス生成時に起動時に大量のメモリが消費される。
そこで共有メモリを利用し、マルチプロセスより軽量なマルチスレッドを使うことになる。
https://github.com/nodejs/node/blob/master/lib/worker_threads.js
Worker
コンストラクタ。実行するファイルのパスが引数。
isMainThread
でマインスレッドか判定。parentPort.postMessage()
メインスレッドにメッセージング。IPC
でのメッセージングではない。
https://github.com/nodejs/node/blob/master/lib/internal/worker.js#L106
メッセージの処理は以下にまとまっている
https://github.com/nodejs/node/blob/master/lib/internal/worker/io.js
https://github.com/nodejs/node/blob/8375c706ad51a399451e4f43b075f3795c440dad/src/node_messaging.cc#L802
HTML structured clone algorithmと互換性のある形式のものが転送される。
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#Supported_types
workerData
JavaScriptの値で、Workerコンストラクタに渡されたデータのクローン。
関連するN-APIは以下なのかな?
workser threadから非同期的にJavaScript関数を呼び出す。
workser threadが即時終了状態か
スレッドセーフ関数のキューがいっぱいになった時にブロックするか
1 | ; |
Functionalコンポーネントからコンポーネントの状態やライフサイクル機能をフックさせるAPI。
後方互換があるため既存のアプリにも将来的に組み込むことが可能。
提供されているReact Hooks apiから独自のHooksを
React Hooks apiを使って無理やりカウンターを作る。
さすがに全ては使うことができないので、以下の機能を試した。
ちなみにReact Hooksは現時点でstableではないので、プロダクションアプリで利用すべきではない。
またTypeScriptなどで利用する場合、必ずしも型情報が充分に提供されているわけではない。
React Hooks apiのリストからわかる通り、useReducer
useContext
あたりを使うことで、将来的にはReduxは必要なくなる感じっぽい。
(Redux Devtoolsなどのコミュニティが作り上げたエコシステムがあるのですぐには無くならないかもしれない)
1 | import * as React from "react"; |
React Hooks自体は単純なStack Queueで管理されているので、Root Componentレンダリングのタイミングで各コンポーネントのHooksが発火していく。
React hooks: not magic, just arraysでも言及されているようにその順序を破壊しないように使う必要がある。
1 | Don’t call Hooks inside loops, conditions, or nested functions. |
React Hooksの導入でアプリのリファクタリングにかける時間的なコストは下げられそう。
またパフォーマンスの問題などこれまでより考慮することが減りそうなのはとてもよい。
Suspense
などからもReact Componentがあたかも生物のように自律的に機能することになっていきそうな感じだな。
Hooks API Reference
Under the hood of React’s hooks system
React hooks: not magic, just arrays
該当の変更は以下を参照
PureComponentはstateをshallow Equal
によってupdateするのかを決める機能を持っているが、class Componentでしか利用できない。
React.memoはfunction componen
をラップしてPureComponentと同じく、shallow Equal
でコンポーネントをupdateする機能を提供するAPIである。
このあたりがそれっぽい
1 | import React from 'react' |
任意のComponentの動的なimportを可能にするAPI。
(ただし現時点ではSSRでは利用できない)Suspense
でfallbackを定義できる。
つまり読み込みが完了してない場合にloading spinnerなどのコンテンツを配置しておくなど。
ネットワークの問題などによりComponentがうまくimportできないときを考慮して、
ErrorBoundyでFallbackしておくことで、ユーザーにエラー通知UIも提供することができる。
以下のような感じ
1 | import React from 'react' |
React.memoに関しては、Component, PureCompoent, memoと比較できるようにして、
React Developer ToolsのHighlight Update
を有効にするとComponentのupdateの具合を比較できます。
HOC(Higher-order component)はcomponentを引数にとり、新しいコンポーネントを返す関数であり、コンポーネント間のコードを再利用可能にする際に利用される手法。
ざっくり以下のような感じのこと。
1 | const composedComponent = hightOrderComponent(Component) |
またはコンポーネントにpropsを渡すなら
1 | const composedComponent = hightOrderComponent(injectProps)(Component) |
という感じのもの。
以下の点が確保されている必要がある。
一般的なものはpropsをproxyするパターン
1 | import React from 'react' |
1 | const propsProxyHOC = ({ name = 'James' }: Partial<Options> = {}) => <OriginalProps extends {}> |
default引数を指定する。引数オブジェクトのnameメンバが省略されている場合は、default値(James)が渡される。
引数自体がない場合は空のオブジェクトが渡るようになる。Partial
keywordを使いOptionsのパラメーターをオプショナルにする。これにより引数オブジェクトのメンバを把握しておく必要性がなくなる。
つまりPartialにより引数の型は以下のようになる。
1 | type Options = { |
render部分では
1 | public render() { |
propsを追加してcomponentを拡張する。
また、componet自体を別要素でwrapすることもできる。
以下のようにして利用する。
1 | const composedComponent = propsProxyHOC({ name: 'John' })(Component) |
またprops proxyするだけではなく、inheritanceすることも可能である。
1 | type ExternalProps = { |
継承することでComponentのlifecycleにアクセスできるようになる。
ということTypescriptでのReact HOCのパターンを触れてみた。
ReactのHigher Order Components詳解 : 実装の2つのパターンと、親Componentとの比較
]]>Reactを使う場合の多くにおいて状態管理はReduxを使うことが主なので、
まずはその周りをまとめておく。
ActionTypesはEnumを使う。
1 | // Before Typescript2.4 |
Typescript2.4からstring Enumsがサポートされてるので、ActionTypesをEnumのメンバーにまとめて定義しておくことで型安全を確保できそう。
Action Creatorは単にActionと呼ばれるObject形式のデータを返す関数であることなので
1 | type Action = ReturnType<typeof ActionCreator> |
という感じでTypescript2.8からサポートされたConditional Types
を使って表現できる。
1 | type AwesomeAction = ReturnType<T> |
ReturnType
は型引数
Action Creatorを定義し、typeof
演算子にて各アクションの型情報を生成する。
こうすることで型宣言は型推論を使って実際の実装から得ることができる。
今回は追加と削除のAction Creatorを作成する。
併せてActionの共用体のaliasを作成しReducerの処理周りで使う。
1 | type AddTodoAction = ReturnType<typeof addTodoActionCreator> |
余談だが抽象的な型表現だと今回のActionの型表現は以下のようになる
1 | interface AppActionWithPayload <T extends string, P extends {} = {}> { |
Reducerも同じくデフォルトの状態の定義から型情報を取得する。
またreducerの返却値はObject.assign
でもいいが、spread operator(スプレッド演算子)も利用できる。
1 | const defaultState: { [index:string]: Array<string> } = { todos: [] } |
あとはStore
を作ればいい
1 | const Store = createStore(reducer) |
ここまででredux周りとしては終わり。
ReactのCompnentと連結していく
作成するアプリはイメージとして以下のようなhtml構造を期待している
1 | <div id="root"> |
ContainerとなるCompoentクラスの定義
1 | type AppProps = { |
Container ComppnentはstatefullなのでinitialState
が必要になる。<1>
のように実際の実装からtypeof
演算子で実装から型情報を得る。
ReactではstateはsetState
でのみ変更する。
そのため、stateを直接変更できないよう<2>
のようにstateをreadonly
修飾子でイミュータブルにしておく。併せて型情報もreadonlyにしている。
PresentationのComponentはstatelessなことが多い。これは基本的にStateless Functional Component
を利用する。
input filed componentに関して
1 | const defaultTodoInputField = { |
defaultTodoInputField
の型情報はTypeScriptの型推論に任せられる。
実際の実装からtypeof演算子にて型情報を得ることができる。
1 | const TodoInputField: React.SFC<TodoInputFieldWithDefaltProps> = (props) => { |
TypeScript3.0からはJSX syntaxでdefaultProps
プロパティを正しく利用できるようになっているので、その恩恵を受けて!
(Non-null assertion operator)を利用する必要はない。
Default props in ES6 class syntax
1 | // これまでは`defaultProps`プロパティとjsxのレンダリングの関連性を |
Componentのマウントのタイミングinputフォームにフォーカスしたり、
フォームの追加ボタン押下後にフォームの入力値を空にするために、
今回はRefを使って要素にアクセスようにしたい。
Forwarding RefsはReact v16.3.0で追加されたAPIでHOC(Higher-order components)を使って、コンポーネント経由(propsを介して)refsを渡せる。
またrefの作成には同じバージョンで導入されたcreateRef
APIを利用する。
そのために App Class
を修正する
1 | class App extends React.Component<AppProps, AppState> { |
さらにinput fieladのコンポーネントもTodoInputFieldWithForwardedRef
を利用する。
1 | const TodoInputField: React.SFC<TodoInputFieldWithDefaltProps> = (props) => { |
ここまででパターンとして新たに見直した箇所は終わり。
一般的にはtyped aliaseではなく、interfaceのほうが拡張性あるので、使うべきであるが、Reactのプロジェクトに限れば外部に公開されるAPI
やサードパーティーの型情報
の場合はinterface
を使う感じで良さそう。
逆にReactアプリケーションデータやcomponentのprppertyやstateなどはtype aliaseを利用する。
Interface vs Type alias in TypeScript 2.7にある、
Componentの拡張はHOCなどのパターンで行うので、interfaceでの継承などはあまり必要ない。という感じの説明がしっくりきた。
また、type aliaseを利用することで、意図しない型情報のマージを避けることもできる点もあるかな。
https://github.com/kazu69/Scripts_Notes/tree/master/react/typescript-react-pattern
]]>まずおさらいをする。
Reactでは親コンポーネントから子コンポーネント、孫コンポーネントにデータを渡す際には、
バケツリレー(Reactドリル)的な方法で参照を渡していく必要があった。
簡単なネストコンポーネントを使っておさらいしてみる。
1 | import React, { Component } from 'react'; |
上記の例のようにコンポーネント間でprops経由でデータを渡す必要がある。
これらを解決する方法として、これまでReduxなどのライブラリを使う必要があった。
React New Contetx APIで状態を管理、参照するには、まずデータストアを定義する必要があります。
このデータはあたかもグローバルな変数のように扱えるため、子コンポーネント、孫コンポーネントと参照を渡す必要はない。
まずcontextを作成する。これによりProviderとConsumerのペアを生成します。
1 | const {Provider, Consumer} = React.createContext(defaultValue) |
今回はカスタムオブジェクトからProviderとConsumerを使います。
1 | const HomoSapinsesContext = React.createContext() |
ProviderをラップするカスタムProviderを作成します。
ProviderはConsumerにデータを提供する機能。
valueプロパティとしてstateを渡している点がそれにあたる。
1 | class HomoSapinsesProvider extends Component { |
Providerの配下に子コンポーネントしてConsumerを配置することで、
グローバルな感じでデータを受け取ることができる。
このデータが変更されるたびにConsumerはレンダリングされることになる。
1 | // Consumerを経由して直接データを参照できている |
このように上位のコンポーネントからデータを受け取らないで直接データにアクセスできる。
ここまでのコードの全体は以下のとおり。
1 | import React, { Component } from 'react'; |
状態の変更にはsetStateで直接状態を変更する。
この辺りのやり方は定まってないため、開発者に依存しそう。
Reduxのように状態変更のためのReducerを作成し、それを利用してみることにします。
1 | const reducer = (prevState, action) => { |
コンポーネントにはdispatchメソッドをもたせて、
Reducerを経由して状態を変更するようにします。
1 | class HomoSapinsesProvider extends Component { |
Provider経由でメソッドを渡すことでConsumerから利用できる。
新しいContext APIは複雑性がなく学習コストが低いため利用しやすいと感じ。
単純なコンポーネントやアプリケーションと比較的結合性の低いコンポーネントなどでは
そのままつかうことでデータの透明性を確保できるtのでいいかもしれない。
公式のドキュメントにもあるとおり、新しいContext APIを使うことでより容易になるパターンもある。
Redux的なアーキテクチャを利用しないと状態管理が複雑になりそうで、実施あの運用はハードルが高そう。
なので、ある程度の規模のアプリケーションや複数人での開発においてはReduxなどに乗っておくほうがいいきがする。
とはいえ、この変更は革新的なので今後は使い分けが出てくるのかなという気がした。
ということで自分のプロダクトでいかほどかおためしして見てみる。
ほとんど公式ドキュメントのななめ良いなんですが、ざっとまとめる。
npm installを実行することで自動的に実行され、セキュリティのレポートが出力される。
(//registry.npmjs.org/-/npm/v1/security/audits/quick あたりのAPIかな)
1 | # upgrade npm@6 |
npm auditはインストールしたnode_module
に対してセキュリティチェックを行いレポートを出力する。
詳細なレポートはnpm audit
を実施することで確認できます。
1 | npm audit |
という感じで、
npm auditでは脆弱性の深刻度とパッケージ、path情報などがレポートされる。
パッケージでの解決が可能な場合は解決方法が提示される。
1 | # Run npm update fsevents --depth 3 to resolve 11 vulnerabilities |
この場合は npm update fsevents --depth 3
を実施する。
1 | ┌──────────────────────────────────────────────────────────────────────────────┐ |
自動で解決できない場合は、マニュアルで確認、調査することが可能。
nodesecurity.ioのリンクが提供されており、
CVE(Common Vulnerabilities and Exposures)情報があるので脆弱性の緩和・解決を図る。
該当するpackageのパスがあるので、必要に応じてパッケージの更新や修正を行う。
例に挙げているレポートではPrototype Pollution としてdeep-extend@0.5.1
以前のバージョンがプロトタイプ汚染に関して脆弱なためv0.5.1以上にアップデートする必要があるので、該当のパッケージ更新を行う。
またバージョンの更新ができなかったり、修正が存在してない場合は以下のいずれかの方法でた右往する。
issue tracker
から脆弱性の情報を報告するまたインストール時にauditを実施しないときは--no-audit
フラグを渡してあげる。
1 | npm i <PACKAGE> --no-audit |
または.npmrcでaudit
をfalse
にしておくことで、すべてのインストールに対してauditは無効化できる。
1 | npm set audit false |
ということで、自分のpackageに対しても結構でてきたので、
セキュリティの意識を高めて開発していきたい。
HoudiniではブラウザのCSS APIを開発者に解放することを目的としている。
最終的にはW3Cでの標準化を目指しているプジジェクト。
APIを使うことで開発者はブラウザベンダーと同じく、自由にブラウザのCSSレンダリングの低レベル部分を操作できるようになり、自由な表現が可能となる。
ドラフトによると以下のAPIで構成されている。
API経由で開発者の書いたコードがブラウザのCSSエンジンのフックされ、カスタムCSSを作成し実行することができる。
カスタムCSS機能はWorkletと呼ばれ、JavaScriptで定義されている。
Workletはブラウザ実行時にJavaScriptとしてブラウザにロードされる。
ユーザーはあたかもブラウザ組み込まれたスタイルのようにスタムCSSを利用できる。
WorkletはWebworkerに似ているが、次の部分が違う。
各ブラザベンダーの実装状況はIs Houdini ready yet‽で知ることができる。
Houdiniに関連するWorkletは次のものがある。
name | content |
---|---|
PaintWorklet | CSSのカスタムプロパティのレンダリングを定義 |
AnimationWorklet | カスタムのアニメーション、スクロールの定義 |
LayoutWorklet | カスタムのレイアウトの定義 |
指定されたURIにあるworkletを取得ためのaddModuleメソッドが提供されている。
1 | <credentials> = 'omit' | 'same-origin' | 'include' |
それぞれに関してかいつまんで見る。
機能は chrome:// flags でExperimental Web Platformの機能を有効にすることが必要になる。
任意のboxに対して描画を行うAPI。CanvasのレンダリングAPIのサブセットを使い描画行う。
以前はCompositorWorkerとして提案されていたもので、
worklet内でカスタムのアニメーションの実行を行う。
こちらはアニメーションのサブクラスを使いアニメーションを行い、複数のタイムラインを持つことができる。
レイアウトやボックスモデルのサイズ、positionなどを開発者で計算ができるようになる。
workletではないが、JavaScriptからCSS単位付きの値(px, %など)演算を行う際に
オーバーヘッドが発生するため、CSSの値を型付きでAPIとして公開し開発者に提供するもの。
例えばこのようなな感じで扱っていた値とユニットを
1 | const elem = docuemnt.queryselector('#element') |
CSSStyleValueをもちいることで以下のように扱うことができる。
1 | const elem = docuemnt.queryselector('#element') |
またユニットの違う場合の計算においても、cssのcalc関数のように取得できる。
1 | // like calc(1em + 5px) |
ボーダースタイルの動的変更
1 |
|
1 | // paint-button.js |
paint(paint-button, orange, 10); でモジュール名と引数を渡す。
workletのcallbackはJavaScriptのclassとなり、引数はinputArgumentsで列挙される。
ここではタイプを指定して取得できる。指定できるタイプはcss-properties-valueのsupported-syntax-stringsにあるものが指定可能。
pain()メソッドでカスタムの描画を行うことができる。
Ripple Button デモ
1 |
|
1 | // ripple.js |
イベントのcallbackと組み合わせることで、tansition効果をつけたエフェクトも可能。
cssのカスタム変数へのアクセスはinputPropertiesで列挙できる。
ということで、これまでのCSSでのリッチUIの提供方法に変化がある未来がすぐそこまで来ている。
Webcomponentsと同じようにビジュアルのモジュール化できるので、これまでのCSS開発手法が変わってきそう。
css-properties-values-api-1
ss-houdini-drafts
Houdini – CSS の秘密を解き明かすもの
簡単な例を示してみてみようと思う。
1 | package client |
のような場合、コンストラクタは普通
1 | fucn NewClient(port, timeout, ua) *Configs { |
とするの一般的。しかし毎回パラメーターを渡す必要がでてくる。
引数なしでインスタンス生成できにない。
そこでコンストラクタを分けるということも考えられる。
1 | fucn NewClient() *Configs { |
オプションが増えることで煩雑になる。
他にはあらかじめ構造体を作って渡すという手法。
こちらは明示的で拡張もしやすい。
1 | type clientConfigs struct { |
一般的なやり方としてはこうなりそう。しかし、オプションを渡す必要な場合でも
構造体を渡す必要がある。
そこでFunctional Option Patternを試す。
1 | type Configs struct { |
オプションを設定する関数を作成し、コンストラクタに可変長引数として渡す。
オプション設定関数はいわゆるクロージャで、設定値を受け取り、オプションを設定するだけになる。
これで、可変長な引数で拡張性のあるオプション設計ができるようになりました。
勉強になりました。
Self-referential functions and the design of options
Functional options for friendly APIs
これにより処理を動的に拡張するメタプログラミングできる。
ということで、参考になりそうな事例があったので試してみた。
caniuseでProxyのサポートを見るとモダンブラウザでは実装済である。
まず、Proxyオブジェクトをおさらいする。
基本構文は次の通り
1 | // target |
targetはProxyで拡張するオブジェクト。
handlerはtrapを含んだオブジェクトとなる。
trapとはオブジェクトのプロパティへのアクセスを提供するメソッド。
上記の構文例だとgetとなる。
getの中ででてくるReflectオブジェクトはトラップされるビルトインメソッド(処理)を提供する。
ちなみにtrapが設定されてない場合は、デフォルトのメソッドが実行される。
ブラウザへの実装が未対応のものもあるが、handlerとtrapとの関係は以下のようになっている。
handler / trap | proxyされるビルトインメソッド |
---|---|
handler.getPrototypeOf() | Object.getPrototypeOf() |
handler.setPrototypeOf() | Object.setPrototypeOf() |
handler.isExtensible() | Object.isExtensible() |
handler.preventExtensions() | Object.preventExtensions() |
handler.getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor() |
handler.defineProperty() | Object.defineProperty() |
handler.has() | in 演算子 (ロパティが指定されたオブジェクトにある) prop in Object |
handler.get() | プロパティ値を取得 |
handler.set() | プロパティ値を設定 |
handler.deleteProperty() | delete 演算子 delete Object.prop |
handler.ownKeys() | Object.getOwnPropertyNames() Object.getOwnPropertySymbols() |
handler.apply() | 関数呼び出し |
handler.construct() | new 演算子 new Object() |
プロパティ変更するメソッドのtrapすることで変更イベントを検知して特定の処理を実施できる。
1 | const onChangeObject = (target, func) => { |
enumもできる
1 | const createEnum = target =>{ |
一定時間キャッシュ可能なオブジェクト
1 | const objectCache = (target, ttl) => { |
objectのプロパティ設定値をvalidateする。
1 | const withValidate = target => { |
シングルトンパターンを実現する
1 | const createSingleton = target => { |
Node.jsをネイティブ拡張するにはNANやV8に依存しているため、
その内部実装に関しての知識が少なからず必要になってくるということが問題となっている。
また、これらはバージョンごとでAPIは安定性が保証されてないので、
ネイティブな機能のリリースには各バージョンでのコンパイルが必要となり、
開発者の負担のかかる状況とのこと。
そこでNode.js8.0からABI-stableなN-APIが試験的に追加されている。
N-APIをつかうことで、一度コンパイルしているモジュールはN-APIをサポートしているNode.jsのどのバージョンでも利用できる。
(現在は8.0以降のサポートだけど、今後は会のバージョンへの移植も検討されている)
N-APIを使うことでV8に依存していたネイティブアドオンをプラットフォームやアーキテクチャごとにビルドする必要がなくなる。
とういうことで、V8とN-APIでネイティブアドオンを作ってみた。
まずはじめにnode-gypを使うのでインストールしておく必要がある。
ChromiumやV8同様にGYP(Generate Your Projects)をつかってプロジェクトファイルを生成し、アドオンを作ります。
(もちろん利用するにはpython <= 2.7も必要)
1 | npm i -g node-gyp |
今回使うC++プログラム。
1 |
|
1 |
|
最終的に以下のようにJavaScriptから呼び出せれば良い。
1 | // test.js |
binding.gyp を作成
1 | { |
V8をそのまま使っている。
1 |
|
プロジェクト作成とビルド
1 | node-gyp configure |
実行する
1 | node test.js |
同じくbinding.gyp を作成
1 | { |
そして、N-APIを使う。
1 |
|
後は同じく、プロジェクト作成してビルド。
1 | node-gyp configure |
実行する際にはオプションが必要。
1 | node --napi-modules test.js |
実際に仕事でこんなことないんですが、サービスによっては複数の言語で動いているとかや、
すでにC/C++で完成している機能をJS側から呼び出したいというとき。
またC/C++のプロジェクトの移植でモジュール開発する際などで使う際はN-APIサポートしておくと捗りそう。
以下のよう現象になった。
brewのたびにエラーが起きるので何もできない。
1 | $ brew |
エラー内容からCURL_CA_BUNDLE あたりを疑い、パスとcurlのバージョン、certあたりを確認し、SSL_CERT_FILEが設定されていることがわかり、unsetするもいっこうに解消しない。
他の環境変数が影響しているのか、結局原因がわからないままになったが、同様の現象でissueがあり、HOMEBREW_ENV_FILTERINGで環境変数をフィルタリングすることを知り試してみると、無事に解消した。
1 | $ HOMEBREW_ENV_FILTERING=1 brew |
HOMEBREW_ENV_FILTERINGに関してはv1.11で追加されており、実装は以下の通り。
https://github.com/Homebrew/brew/blob/b2e2e4b917805b8cf0a86bbc4a1517146a1f0d33/bin/brew#L61-L71
1 | if [[ -n "$HOMEBREW_ENV_FILTERING" ]] |
で実際に手元でやってみると以下のようになる。
1 | $ HOMEBREW_ENV_FILTERING=1 brew config |
1 | $ brew config |
1 | 15c15 |
というように手元の環境をフィルタリングしてデフォルトのものを使う感じになのかな。
まずbrewがトラブった場合は試してみるとよさそう。
必ずSegmentation Faultが起きるようにphpを拡張して、デバッグしていく。
まずSegmentation Faultとなるようにphpを拡張する。
1 | # php取得 |
生成されたconfig.m4を編集する。
以下の箇所をコメントインする
1 | /* config.m4 */ |
拡張モジュールをビルドのためにphpizeする
1 | phpize |
example.cに拡張を追加していく。
拡張を実装する。
1 | /* example.c */ |
makeする
1 | make |
一応これで必ずSegmentation Faultふがおきるphpのメソッドができた。
いろいろとコアな変更もしたいので privileged でコンテナを起動。
コアファイルへのアクセスやプロセスのtraceなどを行うため、コンテナ起動時にはcapabilityの追加の追加をしておく。
またcoreファイルのシステムリソールを変更する(unlimitedではエラーになるので十分に大きな数字を渡す)。
コンテナ内のシステムコールへのアクセスがセキュリティ的にだめなので、security-opt オプションを追加。
1 | # build and run container |
デバッグの際によく使われるのがstrace。
プロセスがどの動きをしているのか(カーネルのシステムコール)をtraceする。
今回はphpで gethostbyaddr を実行してそれをtraceしてみる。
わかりやすいようにapacheのコプロセスを1つにしている。
1 | # -T 各システムコールにかかった時間 |
なんとなく TCPで53ポートで通信して、
HTTP/1.1でレスポンスが来ていることなどもわかる。
プロセスの動きをトレースすることでプログラムとカーネル側かの問題を切り分けることができる。
最初に使った必ずエラーとなるphpを実行してSegmentation Faultを起こし、
coreファイルを出力してgdbでデバッグしてみる。
coreファイルはダンプした時点でのメモリ内容をそのまま記録しているので、そこから原因を特定できる。
1 | # php moduleが読込まれていること確認 |
apacheなどのプロセスが長時間実行されている場合などは、
gcore(generate-core-file)でcoreダンプファイルを作成してみる。
gdbでプロセスにアタッチすることでデバッグはできるが、gdbがプロセス制御を奪うため、next, step, run, continueなどでプログラムを実行しないと停止している状態となるため稼働中のapcheのプロセスなどには使えない。
gcoreを使った場合はcoreダンプファイルを作成している間だけattachされるため、プログラムの停止を最小に止めることができる。
1 | gcore 16 |
またSegmentation Faultが起きる場合はプロセスいなくなるので、あらかじめcoreファイルを出力するように設定しておくこともある。
この場合、エラーが発生すると所定のcoreファイルが出力される。
今回はphpで実際にSegmentation Faultを起こしてcoreファイルを吐き出す。
1 | curl http://localhost:8080/error.php |
gdbでcoreファイルをデバッグ
1 | gdb /usr/sbin/apache2 -c core.cc0a7a572fde.apache2.1505406197 |
上記の内容から想定通りexample.c 72行目で落ちていることがわかる。
普段アプリケーションレイヤーを開発している人からすると、だいたいこの辺かなという感じで問題点を追うことができるが、はっきりとして原因を掴むために、
straceやgdbなどで追求できるようにしておくことは良いことだと思われた。
低レイヤーでの開発している人にとっては目新しいことではないでしょうが…
example-debug-apache-service-operating
PHPでのデバッグ方法
PHP Extension 開発入門
Apache HTTPD Debugging Guide - The Apache HTTP Server Project
ざっくり復習のためメモリ領域についてまとめる。
メモリ領域は
メモリ領域 | 内容 |
---|---|
プログラム領域 | マシン語に変換されたプログラムが格納される。この機械語を1行ずつ実行することで、プログラムが実行される。 |
静的領域 | グローバル変数や静的変数などが入る。プログラム実行時にメモリ領域が確保される。メモリサイズは固定される。 |
ヒープ領域 | 塊という意味。プログラム実行時に確保されるが、実行時にしかメモリサイズがわからないので任意のサイズとなる。C言語のmalloc関数やnew演算子などで確保・管理される。新たにデータ猟奇が必要になると、未使用のメモリ領域を(飛び飛びになってしまうこともある)統合し、ノードに還元する。あらかじめ大きな確保する場合などに使われる。 |
スタック領域 | 積み重ねるという意味。一時変数、関数の引数、返り値などが一時的に格納される。メモリのサイズは固定で最後に積まれたメモリから最初に解放される(後入れ先だし: FILO)に則る。変数を定義しすぎたりするとメモリ領域を超えてオーバーフローする(スタックオーバーフロー)。現在のスコープで必要としている領域だけ確保できれば良い。 |
ヒープで確保されたメモリが不要になった場合、プログラム側で開放できてないと猟奇が確保されたままになる(メモリリーク)。プログラムで開放するのではなく自動で開放する仕組みを持っているものもある。
この機能をガベージ・コレクトという。
Golangのメモリ管理を見てみる。
goではビルド時に -gcflags -mフラグを渡すことでコードを解析できる。
ということで試してみる。
まずローカル変数を返すだけ
1 | package main |
1 | # command-line-arguments |
この場合はスタックを使っている。
つづいてメモリのアドレスを返してみる。
1 | func main() { |
1 | # command-line-arguments |
この場合は、ヒープに置かれた。
アドレスからポインタの中身にアクセスする場合
1 | func main() { |
1 | # command-line-arguments |
この場合はスタックに置かれる。
1 | func main() { |
1 | # command-line-arguments |
ポインタを返すのでヒープに置かれる。
new演算子でメモリを確保してポインタを返さない場合
1 | func main() { |
1 | # command-line-arguments |
関数内でのみ使われるだけなので、スタックに置かれている。
ローカル変数でも他の関数に渡すと
1 | func main() { |
1 | # command-line-arguments |
ヒープに置かれている。
続いて構造体を使ってみる。
シンプルに構造体を返すだけ
1 | type Human struct { |
1 | # command-line-arguments |
スタックに置かれている。
1 | func main() { |
1 | # command-line-arguments |
ポインタを返すのでヒープに置かれている。
new演算子を使っても関数内で処理が終わってる場合
1 | func main() { |
1 | # command-line-arguments |
変数はスタックに置かれている。
ポインタを保存した変数を返す場合
1 | func main() { |
1 | # command-line-arguments |
ヒープに置かれる。
Goの場合は、基本的にスタック領域を使うよう試みる。
ローカルの変数でも外部関数に渡されたり、ポインタを返す場合はヒープ領域が使われる。
これはローカル変数がスコープとなる関数の処理終了後も参照される可能性があるためらしい。
new演算子によるメモリの割り当ては必ずしもヒープを使うわけではない。
ということで、関数内で参照のみされる変数は実態を渡すことで、
アプリケーションとしてのパフォーマンスが良くなるということがわかった。
GCに関してはまた別で調べてみたい。
react-routerはv3と大きく違うため、正式なドキュメントを参考にしないとv4以前のものと混同してしまっておそらくはまってしうんじゃないかな。
まず、routerは大きく3つのパッケージに分割されている。
package | description |
---|---|
react-router | core package |
react-router-dom | react-routerとdomをbinding |
react-router-native | react-routerとreact-nativeをbinding |
今回はウェブアプリケーションを利用するため react-router-dom
を使ってみた。
このpackageには BrowserRouter
と HashRouter
、 Link
、NavLink
コンポーネントがある。
アプリケーションに動的なURIが含まれる場合は BrowserRouter
を利用する。こちらはbrowserのHistory APIをサポートしている。HashRouter
はwindow.location.hashを使ってルーティングと同期している。MemoryRouter
(URLは変更しないで履歴をメモリ管理するルーター) などcore packageでザポートされているrouterも利用は可能っぽい。
BrowserRouter
を使う場合
1 | import { BrowserRouter, Route } from 'react-router-dom' |
v4ではrouteで指定したpathとlocaltion.pathname
が一致したcomponetをレンダリングする。
例えば以下ような場合に
1 | <Switch> |
/hoge
へのアクセスで
/
がマッチするため HomeComponent
をレンダリング/hoge
がマッチするため HogeComponent
をレンダリングFugaComponent
はレンダリングされないとなる。
pathnameなのでurlのパラメーターなどは関係しない。
パスのマッチングには path-to-regexp パッケージが使われている。
### routeコンポーネント作成
実際のルーティング部分を作成する。先ほどの例では <App />
コンポーネントの中のいずれかのコンポーネント内で実際のルーティング処理を記述することになる。
1 | <Switch> |
exact
プロパティは location.pathname
と値が正確に一致した場合のみの適用される。
Routeコンポーネントのレンダリングには3つの方法がある。
指定されたコンポーネントをReact.createElement
で作成するため、新しいコンポーネントを作成します。
1 | <Route path="/user/:username" component={User}/> |
comonentをマウントしないで、インラインでレンダリングすることができる。
ただし component が優先されるらしいので同じ Route
で両者を使用するのは避けたほうがいい。
レンダリングするコンポーネントへpropsを渡す時などはこちらを使うとやりやすい感じ。
1 | // convenient inline rendering |
pathが条件に一致するかどうかをレンダリングしたい場合などは、children
propを使う。
pathと一致するしないに関わらず常にレンダリングされる。
1 | <ul> |
動的なURIの場合、パラメーターはコンポーネントで取得する必要がある。
1 | <Switch> |
上記のような場合、/edit/:id
で id
をコンポーネントで受け取る場合。
1 | const EditComponent = props => { |
props.match.params
で取得できる。
最後にページ遷移を行う場合にLink
コンポーネントを利用する。
これによりURLが更新されて、ページをリロードすることなくコンポーネントがレンダリングすることができる。
1 | import { Link } from 'react-router-dom' |
URLの変更を検知して、popstateを発火しているので、ページ移動のたびにコンポーネントを再描画できるっぽい。
(history.listenを利用)
また、ナビゲーションなど該当ページのURLとリンク先URLがマッチした時にスタイルを適用するとかの処理が必要な場合は、NavLink
コンポーネントを使うといい感じ。
1 | import { NavLink } from 'react-router-dom' |
v3からワイルドに変更されていることで有名なんですが、
Roterが小さなコンポーネントで分割構成されていることで、Reactを使っていると、ルーティング周りは実装しやすい。
ルーター経由でコンポーネントにpropsを渡す時にrenderすることが最適解なのか未だにわかっていない感がある。
react-redux-router-ssr-example
React Router: Declarative Routing for React.js
ReactTraining/react-router