yn2011's blog

技術メモ

使用しているAlfred Workflows一覧

環境

  • Alfred 4.0.3

Alfred Powepackを利用している場合は2台のMacでAlfredの設定を同期することも可能(ドキュメント)だが、1台からの片方向同期かつWorkflowsだけを同期するのは難しそうなので、業務で使用するMacとは手作業で同期を行うことにしようと思った。(JIRAチケットへのジャンプ等、業務でしか使わない設定も少なくないので...)

今後も良さそうなWorkflowsがあったら、このドキュメントに追加していく。

Workflows

github.com

github.com

github.com

github.com

(自作)フォルダを検索してターミナルで開く

  • filefilter(public.folder) → Browse in Terminal

Docker Compose上で起動したwebpack-dev-serverに設定されたプロキシが動作しなくてハマった話

環境

  • macOS 10.14.4
  • docker-compose version 1.23.2
  • webpack-dev-server 3.7.2
  • webpack 4.37.0

フロントエンド(webpack-dev-server)、バックエンドをそれぞれDocker Compose上にコンテナとして起動して開発していた際にハマったお話。

事象

  • webpack-dev-serverはホストするjs内で発行されるHTTPリクエストをプロキシすることができる

    • 今回なぜプロキシが必要だったかというと、Chromelocalhost上ではfetch('http://localhost')...のようにlocalhostに対してリクエストを行うとCORSエラーが発生するため
    • jsのソースコード上ではfetch('/api/hoge')..のようにしておいて、webpack-dev-serverにlocalhostへプロキシさせることによって回避できる。(他にもブラウザの設定を変更する等の回避策があるが、webpack-dev-serverに設定されている方が他の人に親切)
  • webpack.config.jsを以下のように設定(抜粋)

    • APIサーバーは5000番ポートを開いているとする
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 8080,
    historyApiFallback: true,
    proxy: {
      '/api': 'http://localhost:5000',
    }
  • 上記設定を反映したwebpack-dev-serverを起動し(docker-compose up)、実際にlocalhost:8080にアクセスしてfetch('/api')...をブラウザから実行すると、webpack-dev-serverが以下のエラーを出力した
[HPM] Error occurred while trying to proxy request /api from localhost:8080 to http://localhost:5000 (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors)
  • httpレスポンスは以下(抜粋)
504 Gateway Timeout

Error occured while trying to proxy to: localhost:8080/api

原因

  • Docker Compose上のコンテナが参加するネットワークでは、docker-compose.yml上のサービス名で名前解決が行われるため。

  • 従って、webpack.config.jsを下記のように書き換えると上手くいく

...
 proxy: {
      '/api': 'http://api:5000',
    }
  • なお、docker-compose.ymlでは以下のようにサービス名をapiとしている
version: "3.4"
services:

  api:
    build:
      context: ./api

...

敗因

  • 書いてみるとこれだけのことだが、多分2,3時間ぐらいハマっていた
  • サービス名で名前解決ができること自体はDocker Composeのドキュメントに書いてあることだし、以前から知っていた。なぜ気づけなかったのか...
    • 多分だが、ブラウザからDocker Compose上のwebpack-dev-serverにアクセスする際は、
      • localhost:8080をDocker Composeのポート8080と接続していて
      • かつ実際にクライアントサイドのjsが動作しているのがローカルだし
      • バックエンドのapiサーバーもlocalhost:5000とDocker Compose上の5000を接続していた
    • のでwebpack-dev-serverが動作しているのがDocker Compose上であることを忘れていたというか、しっかり認識できていなかったような気がする。
    • また、Docker Compose上のネットワーク内のコンテナ間もlocalhostで通信できるようなイメージだったのも原因かもしれない。
  • あと、ステータスコード504 Gateway Timeoutの理解が不足している感もある。webpack-dev-serverがlocalhost:5000が存在しないためにレスポンスを得られない→ポート5000で動作しているはずのHTTPサーバーのログを確認する→ログがない→接続先がおかしい?というような手順をスムーズに踏めたら良かった。
    • Goで書いているHTTPサーバは外部ファイルへのログ出力を未実装で、結局調査のために書いたんだけど、ここを面倒くさがらずに早く実行するべきだったかな...

github.com

bashコマンドは読取権限のあるファイルなら処理できる

環境

Permission denied

  • $ touch hoge.sh && ./hoge.shするとbash: ./hoge.sh: Permission deniedになる
    • 実行権限がない
  • しかし$ touch hoge.sh && bash hoge.shは正常終了するのはなぜか?

読取権限

  • $ chmod u-r hoge.shして所有者の読取権限を剥奪するとbash hoge.shbash: ./hoge.sh: Permission deniedとなる
  • bashコマンドはファイルから読み取った内容をbashコマンドが実行しているのに対して、.コマンドはファイルから読み取った内容を実行する際にOSの実行権限が必要になっている模様(推論、あまり詳しくないので違っていたら教えてください)

httptest.Serverとhttptest.ResponseRecorderの使い分け

環境

go1.12.4 darwin/amd64

httptest.Serverとhttptest.ResponseRecorder

httptest.Server

  • httptest.Serverは外部のHTTPサーバと通信を行うコードをテストするために利用する(外部APIの挙動のエミュレート)
    • クライアント-サーバモデルで言うと、テストしたい対象がクライアントの場合に相当する模様

httptestパッケージより引用

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httptest"
)

func main() {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()

    res, err := http.Get(ts.URL)
    if err != nil {
        log.Fatal(err)
    }
    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", greeting)
}

httptest.NewRecorder

  • httptest.NewRecorderは純粋にHTTPリクエストに対するハンドラーの動作をテストするために利用する
    • 例えばHello worldを返すだけのWebサーバのコードを書いた場合のテストコードはhttptest.NewRecorderで十分

httptestパッケージより引用

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
)

func main() {
    handler := func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "<html><body>Hello World!</body></html>")
    }

    req := httptest.NewRequest("GET", "http://example.com/foo", nil)
    w := httptest.NewRecorder()
    handler(w, req)

    resp := w.Result()
    body, _ := ioutil.ReadAll(resp.Body)

    fmt.Println(resp.StatusCode)
    fmt.Println(resp.Header.Get("Content-Type"))
    fmt.Println(string(body))

}

関連:NewRecorderを利用する場合にserveHTTPを使用する必要があるのか

  • http.HandlerFunc(articlesHandler).ServeHTTP(rr, req)してレスポンスを検証している例もある
  • マルチプレクサ経由でhandlerを呼び出している(HandleFuncはマルチプレクサ(ServeMux)を返し、ServeHTTPはマルチプレクサに登録されているパターンにマッチするハンドラを呼び出す)
    • 結局テストコード中でハンドラを登録しているなら初めからServerを使うか、ハンドラの処理だけにフォーカスするかした方が良いのではないか?(Goのhttpパッケージをテストしたいわけではない)

参考

draw.ioで両端が矢印の線を引く方法

www.draw.io

AWSの構成図をdraw.ioで描いていて、両端が矢印の線を引く方法が分からなかった。もう諦めてCacooを使おうかと思っていたところ解決策が見つかった。

How to create a bidirectional arrow? : draw.io Helpdesk

ちなみに上記で操作しているサイドバーはcmd+shift+pで表示できる(Macの場合)

VSCodeでGoのデバッグ

今更感はあるがVSCodeでGoのソースコードデバッグしてみたので記録。

環境

delveインストール

github.com

VSCodeにテスト対象の$GOPATHを認識させる

  • 方法は複数あるが、VSCodeのworkspaceディレクトリを自動的に$GOPATHとして認識してくれるようにするのが1番手軽そう

launch.jsonを作る

  • テスト対象のプロジェクトをVSCodeで開き、[構成の追加]をクリック(画像は既に構成追加済み)

f:id:pokuwagata:20190615215335p:plain

  • launch.jsonが作成される(作成済みの場合は追記される)
{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${fileDirname}",
      "env": {},
      "args": []
    }
  ]
}

デバッグする

  • breakpointを置く
  • テストコードの[debug test]をクリックする。テストメソッド単位で実行できる。

f:id:pokuwagata:20190615220016p:plain

  • breakpointの位置で処理が止まる。テストメソッドの中でも止められるし、テストメソッドから呼ばれた先のメソッドの中でも止められる。
  • これでテストが通らなくなった場合の原因調査がスムーズに

f:id:pokuwagata:20190615220221p:plain

まとめ

  • VSCodeソースコードのdebugを行う際は、launch.json等の設定ファイルへの習熟が必要な印象があったが、Goの場合はあまり考えることがない
  • delveは必要だったが、それ以外はVSCodeとGo本体というシンプルな構成でデバッグ可能

Goやっていきましょう

型アサーションでインターフェイス型から本来の型を取り出す際の演算子*

環境

インターフェイス型の型アサーション

Goでは、以下のようにインターフェイスエイリアスであるStatement型の変数から実際の型であるExpressionStatementを型アサーション.(T)によって取り出すことができる。

package main

import "fmt"

type Statement interface {
    // [...]
    statementNode()
}

type ExpressionStatement struct {
    // [...]
    hoge string
}

func (es *ExpressionStatement) statementNode() {
    // [...]
}

func getExpressionStatement() Statement {
    return &ExpressionStatement{hoge: "hoge"}
}

func main() {

    exStmt := getExpressionStatement()

    stmt, ok := exStmt.(*ExpressionStatement)

    fmt.Println(stmt.hoge)
    fmt.Println(ok)

}

演算子*の意味

ところで、実際に型アサーションを行っているstmt, ok := exStmt.(*ExpressionStatement)*はどういう意味なのか。

最初は、式の中なのでポインタ型変数の中身を参照(dereference)しているのか、と思ったがExpressionStatementはポインタ型変数ではない。

アサーションは型名を引数に取ることを思い出すと、構造体のエイリアス型であるExpressionStatementのポインタ型という意味なのだろうか...? (そうかもしれない)

もしくはこの場合の演算子*は既存の規則とは分けて考えるべきなのだろうか。

気になる。