Goのnet/httpパッケージでHello WorldするだけのWebアプリ

本記事の目的

Goを学ぼうかどうか判断するために、簡単なWebアプリを作ってみます (アクセスしたら、Hello Worldと表示するだけ)。

開発環境

Goを信用して良いのか分からなかったので、環境を汚さないために、Dockerでやります。 面白かったら、デバッカーとかで内部まで見たいので、ローカルで環境構築します。

ディレクトリ構成は以下のようにしました。 compileFileディレクトリ内にgoファイルを置いて、ビルドしていきます。

compileFile (ディレクトリ)
Dockerfile
docker-compose.yml
# Dockerfile
FROM golang:latest
RUN mkdir -p /go/src
version: '3'
services:
  go:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - ./compileFile:/go/src
    working_dir: /go/src

コンテナ内に入って、goのコマンドを確かめてみます。 --service-portsを指定しないと、8080ポート同士を繋げることはできないので、注意です。 docker psでちゃんとポートフォワードできていることを確認しておきます。

$ docker-compose run --service-ports go /bin/bash
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
af4f9c066ecd        golean_go           "/bin/bash"         7 seconds ago       Up 2 seconds        0.0.0.0:8080->8080/tcp   golean_go_run_a79a836040ab

ひとまず、go と打ってみて、使えるコマンドを確認します。

Go is a tool for managing Go source code.

Usage:

    go <command> [arguments]

The commands are:

    bug         start a bug report
    build       compile packages and dependencies
    clean       remove object files and cached files
    doc         show documentation for package or symbol
    env         print Go environment information
    fix         update packages to use new APIs
    fmt         gofmt (reformat) package sources
    generate    generate Go files by processing source
    get         add dependencies to current module and install them
    install     compile and install packages and dependencies
    list        list packages or modules
    mod         module maintenance
    run         compile and run Go program
    test        test packages
    tool        run specified go tool
    version     print Go version
    vet         report likely mistakes in packages

Use "go help <command>" for more information about a command.

Additional help topics:

    buildconstraint build constraints
    buildmode       build modes
    c               calling between Go and C
    cache           build and test caching
    environment     environment variables
    filetype        file types
    go.mod          the go.mod file
    gopath          GOPATH environment variable
    gopath-get      legacy GOPATH go get
    goproxy         module proxy protocol
    importpath      import path syntax
    modules         modules, module versions, and more
    module-get      module-aware go get
    module-auth     module authentication using go.sum
    module-private  module configuration for non-public modules
    packages        package lists and patterns
    testflag        testing flags
    testfunc        testing functions

Use "go help <topic>" for more information about that topic.

go build go run go fmtは近いうちに使いそうです。

プロジェクト作成

馴染みが深いWebアプリからGoを理解していこうと思いました。 サンプルコードとか見ながら、Hello Worldをブラウザ上に表示するプログラムを書きます。

package main

import "io"
import "net/http"

func mainHandler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, `Hello World!!!`)
}

func main(){
    http.HandleFunc("/main", mainHandler)
    http.ListenAndServe(":8080", nil)
}

1コマンドで整形できるので、整形後ビルドします! ちなみに、ビルド時にリンターも働いているのか、改行の位置や使ってないパッケージがあると怒られます。

ビルドすると、goファイルのあるディレクトリ名の実行ファイルが作られました。

$ go fmt
$ go build
$ ls
server main.go
$ ./server

http://localhost:8080/main にアクセスすると、Hello Worldを確認できました!

スクリーンショット 2020-10-13 1.23.10.png

ちなみに、Macで動かしたい場合は、以下でMac用にコンパイルできます。

$ GOOS=darwin GOARCH=amd64 go build

net/httpパッケージ

わかりやすいところから見ていきます。 これでサーバーを立てられるようです。 第一引数がポートの指定、第二引数でHandlerを選択できるようで、nilの場合デフォルトのDefaultServeMuxが使われるようです。

http.ListenAndServe(":8080", nil)

DefaultServeMuxって何?

ドキュメントを読み進めると、以下の記述にあたります。

HandleFunc registers the handler function for the given pattern in the DefaultServeMux.
The documentation for ServeMux explains how patterns are matched.

HandleFuncでURLのパターンに対応するハンドラーをDefaultServerMuxに登録できる...みたいなことが書いてあります。ルーティング的な何かと思っておきましょうか。

なので、以下は/mainにアクセスしたら、mainHandlerの処理を実行と読めます。

http.HandleFunc("/main", mainHandler)

ioパッケージの方は何してるか分かるので、置いておきます。

document Package http

Localに導入

brewで簡単に入ります。

$ brew install go
$ go version
go version go1.15.3 darwin/amd64

REPLも入れておきます。 gore v0.5.0だと動かなかったので、バージョンを落としています。

# これをしてインストールできるようになりましたが、いらないかもです。
$ export GO111MODULE=on
$ go get github.com/motemen/gore/cmd/gore@v0.4.0
$ gore -version
gore 0.4.0 (rev: HEAD/go1.15.3)

間違えてインストールした場合は以下で削除できます。

$ go clean -i github.com/motemen/gore/cmd/gore

ちなみに、v0.5.0だと以下のエラーメッセージがでます。

gore: could not load 'fmt' package: err: exit status 1: stderr: build flag -mod=mod only valid when using modules

これでローカル環境も整ったので、あとは遊ぶだけです。

総括

Go楽しいですね。 ターミナルアプリ (TUI) とか作ってみたい。

雑多な感想

Qiitaで投稿しようかと思ったけど、Qiitaは怖い人ばかりで治安も悪いので、故郷のHatena Blogでひっそり過ごすことにした。

Terminalで顧客情報を取得する!

What

JSforceを使って、顧客情報をTerminal上で取得してみます。

JSforce https://jsforce.github.io/

How

JSforceの導入

まず、セットアップします。

$ sudo npm install jsforce
$ jsforce --version
1.9.3

Salesforceにログイン

パスワードとセキュリティトークンは続け様に書かないと、ログインできません。

$ jsforce
$ login(('<ユーザー名 (ログイン時に使用したEmail)>', '<パスワード><セキュリティトークン>')

[参考] https://developer.salesforce.com/forums/?id=906F0000000ApdZIAS

Queryを実行

公式のリファレンスを参考にクエリを書きます。

例えば、リードユーザーの名前、電話番号を取得したい場合、次のように書きます。

$ query("SELECT Name, Phone from Lead LIMIT 3")

結果はJSONで返ります。

{
  totalSize: 3,
  done: true,
  records: [
    {
      attributes: [Object],
      Name: 'Dadio Jr Bill',
      Phone: '(614) 431-xx00'
    },
    {
      attributes: [Object],
      Name: 'Luce Eugena',
      Phone: '(781) 270-xx00'
    },
    {
      attributes: [Object],
      Name: 'Eberhard Sandra',
      Phone: '(626) 4xx-0700'
    }
  ]
}

[公式リファレンス] https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_sosl_intro.htm

遭遇したエラー

API is not enabled for this Organization or Partner

API_DISABLED_FOR_ORG: API is not enabled for this Organization or Partner

APIを許可したユーザーでない場合、発生するエラーです。

管理者がAPIを無効にしているか (デフォルトでは有効)、Trial版の場合発生します。

Trial版で試してる場合は、Developer版に切り替えましょう。無料です。

Developer版はこちらから取得できます。

[参考] https://trailblazers.salesforce.com/answers?id=90630000000hxbhAAA

errorCode: 'MALFORMED_QUERY'

上記のクエリにWHERE句を入れようとすると発生します。

$ query("SELECT Name, Phone from Lead LIMIT 4 WHERE Phone='(626) 4xx-0700'")

項目が多い場合やクエリが複雑な時に発生するようです。

[参考] https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select.htm?search_text=MALFORMED_QUERY

JavaScriptで環境変数を使う in Rails

What

Rails + Vue.jsという構成のアプリにおいて、JavaScript (.jsや.vue) でも環境変数を使えるようにする。

Solution

Rails側で.env以下に環境変数をまとめていることを前提に記述します。

.envファイル

API_KEY=jofdashouhaemfaspo3u9fasnifgfa98

まず、yarnでdotenvをアプリに追加します。

$ yarn add dotenv

次に、config/webpack/development.js や config/webpack/production.jsに以下を追記します (一番上に追記しました)。

require('dotenv').config()

あとは使いたい.vueファイルで以下のようにして、呼び出します。

const { API_KEY } = process.env
console.warn(API_KEY)

ng newにてThe Schematic workflow failed.

What

ng newコマンド実行時、以下のエラー群が発生しました。

⠏ Installing packages...npm WARN deprecated tslint@6.1.2: TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.
npm WARN deprecated chokidar@2.1.8: Upgrade to chokidar 3 with 15x less dependencies. Chokidar 2 will break on node v14.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm ERR! Unexpected end of JSON input while parsing near '...://registry.npmjs.org'

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/apple/.npm/_logs/2020-07-03T13_33_59_631Z-debug.log
✖ Package install failed, see above.
The Schematic workflow failed. See above.

この状態で、ng serveを実行すると以下のエラーが発生します。

An unhandled exception occurred: Cannot find module '@angular-devkit/build-angular/package.json'
Require stack:
- /Users/apple/.config/yarn/global/node_modules/@angular-devkit/architect/node/node-modules-architect-host.js
- /Users/apple/.config/yarn/global/node_modules/@angular-devkit/architect/node/index.js
- /Users/apple/.config/yarn/global/node_modules/@angular/cli/models/architect-command.js
- /Users/apple/.config/yarn/global/node_modules/@angular/cli/commands/serve-impl.js
- /Users/apple/.config/yarn/global/node_modules/@angular-devkit/schematics/tools/export-ref.js
- /Users/apple/.config/yarn/global/node_modules/@angular-devkit/schematics/tools/index.js
- /Users/apple/.config/yarn/global/node_modules/@angular/cli/utilities/json-schema.js
- /Users/apple/.config/yarn/global/node_modules/@angular/cli/models/command-runner.js
- /Users/apple/.config/yarn/global/node_modules/@angular/cli/lib/cli/index.js
- /Users/apple/.config/yarn/global/node_modules/@angular/cli/lib/init.js
- /Users/apple/.config/yarn/global/node_modules/@angular/cli/bin/ng
See "/private/var/folders/yt/wcyrx93n3jd93smbhblqjrx40000gn/T/ng-TtYLQM/angular-errors.log" for further details.

Solution

Cannot find module '@angular-devkit/build-angular/package.json'とのことなので、以下のコマンドで@angular-devkit/build-angularを導入します。これで、ng newおよびng serveでのエラーが解決されました。

yarn add @angular-devkit/build-angular --dev

もし、npmの認証系でエラーが生じるなら以下も実行しておきます (下記の記事も参考になりました)。

$ sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}

[参考] qiita.com

MySQL8.0インストール時の諸々のエラー解決

What

MySQL8.0インストール時に詰まったので、その対策等を記録しておきます。

MySQL8.0のインストール

Homebrew経由で取得します。デフォルトで最新版をインストールすることができます。

$ brew install mysql

パスワードの再設定

MySQLが起動しているなら、rootユーザーとしてログインできます。初期ではパスワードが設定されていないため、Enterでログインできます。

$ mysql -u root -p 

MySQL8.0は5.x以前と比べて関数が変更されているため、以下の手順でパスワードを変更します。

mysql > user mysql;
mysql >  alter user 'root'@'localhost' identified by '<設定したいパスワード>';
mysql > exit;

[参考]

qiita.com

bundle install

Rails6.0でMySQLを使用する場合、以下のエラーがでて、インストールできないことがあります。

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.

指示通りコマンドを実行します。

$ gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'

またしてもエラーです。

Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load

pathが合ってないみたいなので、以下のコマンドで詳しくみてやります。

$ brew info openssl

ビンゴっぽい文言が出てきました (親切で嬉しい)。

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

ホームディレクトリに.zshrcファイルを作成 (または既存のファイルを利用) して以上のものを追記しておきます。

$ sudo vi ~/.zshrc
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"
export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

これで、bundle installやrails db:create等ができるはずです。

MySQLの中身を見る

RubyMineだと簡単に見れるらしいのですが、VSCodeを使用している (乗り換えようかなぁ) ので、別の方法で見ます。

結論ですが、TablePlusがオススメです。セキュリティ認証レベルを昔のに戻すと、Sequel Proでも使用できるのですが、せっかく登録したrootユーザーのパスワードがぱーになったりして、困ったので、別ツールを使用することにしました。ちなみに、Sequel Pro build版でもMySQL8.0のセキュリティ認証 (caching_sha2_password) に未対応でした (2020.07.01 現在)。

[参考] https://www.tcmobile.jp/dev_blog/db/sequelpro%E3%81%8Cmysql%E3%81%AB%E7%B9%8B%E3%81%8C%E3%82%89%E3%81%AA%E3%81%84%E3%81%AE%E3%81%A7tableplus%E3%81%AB%E4%B9%97%E3%82%8A%E6%8F%9B%E3%81%88%E3%81%9F/

Selenium::WebDriver::Error::WebDriverError ~Unable to find Mozilla geckodriver.~

What

Capybaraにて、JavaScriptを扱う際、以下のエラーが出てきました。

Selenium::WebDriver::Error::WebDriverError:
Unable to find Mozilla geckodriver. Please download the server from https://github.com/mozilla/geckodriver/releases and place it somewhere on your PATH. More info at https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver.

JavaScriptはブラウザに依存するため、上記のようなエラーがでています。
Firefoxを使用する場合は、上記エラー文通り対処すれば解決できそうです。
Chromeで使用したいなら、以下のgemを追加する必要があります。また、Rails5にてデフォルトで入っているgem 'selenium-webdriver'はそのままでも大丈夫です。

group :test do
  gem 'webdrivers'
end

spec/rails_helper.rbにて以下を追記します。

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f}

spec/support/*.rbにて、以下を追記すれば、エラーが解消されます。以下の記述では、実際にChromeが起動し、自動でテストが実行されます。

Capybara.javascript_driver = :selenium_chrome

もし、ヘッドレスで実行させたい場合は、以下のようにします。

Capybara.javascript_driver = :selenium_chrome_headless

配列と連想配列 ~会津オンラインALDS1_4Cを通して~

What

会津オンラインの以下の問題を解きます。 onlinejudge.u-aizu.ac.jp

Solution

配列で書く方法を真っ先に思いついたので、それで実装してみました。

function main(lines) {
    const input = lines.split('\n').slice(1)
    const n = input.length
    
    let str = []
    for(let i=0; i<n; i++){    
        const judge = input[i].split(' ')
        if(judge[0] === 'insert'){
            str.push(judge[1])
        }        
        if(judge[0] === 'find'){
            console.log(str.includes(judge[1]) ? 'yes' : 'no')
        }
     }
}

main(require('fs').readFileSync('/dev/stdin', 'utf8'));

for文の代わりに、forEachメソッドを使うこともできます。

for(let i=0; i<n; i++){    
  const judge = input[i].split(' ')
}
input.forEach(function(element){
  const judge = element.split(' ')
}

ただ、上記の方法では処理に時間がかかるため、問題をクリアできません。 しかし連想配列で実装すると、処理速度が1.5倍速くなり、問題をクリアできます (処理時間はconsole.time, console.timeEndメソッドで簡単に調べています)。

function main(lines) {
    const input = lines.split('\n').slice(1)
    const n = input.length
    
    let obj = {}
    input.forEach(function(arr){
        const judge = arr.split(' ')

        if(judge[0] === 'insert'){
            obj[judge[1]]= true
        }
        if(judge[0] === 'find'){
            console.log(obj.hasOwnProperty(judge[1]) ? 'yes' : 'no')
        }
    })
}

main(require('fs').readFileSync('/dev/stdin', 'utf8'));

Why

なぜ、連想配列にすると処理速度が上がるのでしょうか。
連想配列と配列の違い、内部での処理について調べる必要がありそうです。
それはまた別の機会に調べて記事にします。

予想できるのは、DBにおけるインデックスと同じように、オブジェクト作成時に仕組みがあるのかもしれません。
実際、配列または連想配列に要素を追加するまでの処理時間では、配列の方が1.3倍程度速いです。