「Webを支える技術」を読んだ(/・ω・)/
「Webを支える技術 -HTTP、URI、HTML、そしてREST-」を読んだ(/・ω・)/
これまでWebサービスを使ったり、実装したりとしてきたけど、改めて体系的にWebについて勉強するといろいろ良かった。 就職前に読んでおいて良かったなーと思った。 Webサービス作り始めの頃とかに読んでおきたかった。
各章で書いてあったことと感じたことをメモしておく。 (自分のメモ書きなのでこれを読めば本一冊読んだことになる的なやつではありません)
1部
1章 Webとは何か
- さまざまなWeb
- Webサイト
- ユーザーインターフェースとしてのWeb(ルータの設定画面など)
- プログラム用APIとしてのWeb
Webっていろんなところで使われてるねって話。
2章 Webの歴史
- Web以前のインターネット・ハイパーメディアの紹介・問題点
- Webの登場と標準化の歴史
Web以前のインターネットとか知らないことが多くておもしろかった。
3章 REST - Webのアーキテクチャスタイル -
- REST = アーキテクチャスタイル
- クライアント/サーバ
- ステートレスサーバ
- キャッシュ
- 統一インターフェース
- 階層化システム
- コードオンデマンド
Web世界の秩序を守るためにもRESTfulなサービスやAPIを作ろう。
2部 URI
4章 URIの仕様
相対URIとかは気にしたことなかったので勉強になった。
5章 URIの設計
リンク先に飛んだら404とか悲しい気持ちになるし、Web的にも良くないよね。 良いURIをつけられるようになろう。
3部HTTP
6章 HTTPの基本
- TCP/IP上で動作するアプリケーション層のプロトコル
- HTTP 1.1が今広く使われている
- リクエストメッセージ
- リクエストライン
- ヘッダ
- ボディ
- レスポンスメッセージ
- ステータスライン
- ヘッダ
- ボディ
- ステートレス
- 利点:サーバはアプリケーション状態を覚える必要がない→シンプル、スケーラブル
- 欠点:パフォーマンスの低下、通信エラーへの対処
HTTPは凄いシンプルなプロトコル。だからこそここまで広く使われているんだろうなーと思った。 一方でシンプルだからこその欠点もあるのでそれは知っておく必要があるしいい勉強になった。
7章 HTTPメソッド
- 8つのメソッド
- GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE、CONNECT
- POSTとPUTの使い分け
- べき等性と安全性
- べき等 = ある操作を何回行っても結果が同じ
- 安全 = 操作対象のリソースの状態を変化させない
べき等と安全は意識しないと、使い勝手の悪いサービスを作ってしまうなーと感じた。
8章 ステータスコード
- ステータスコードの重要性
- クライアントの挙動を決める
- 間違ったステータスコードはクライアントを混乱させる
- ステータスコードは自作できる
- クライアントが未知のステータスコードを受け取った場合は、先頭数字を見て最低限の処理をおこなう
良くみるステータスコードは勝手に覚えてるけど、それ以外のものはいつかゆっくり見てみたい。
9章 HTTPヘッダ
HTTPがシンプルに設計され、かつ広く使われているのはこのヘッダが大きな役割をしてるなと感じた。 上手く使いこなせるようにまずはどんなヘッダがあるかを知らないとなーと。
4部 ハイパーメディアフォーマット
10章 HTML
HTMLの本当の基本しか書かれていなかったので知ってることが多かった。 XMLの名前空間の話とかは見たことあったけどよくわかってなかったので勉強になった。
11章 microformats
- セマンティックWeb
- セマンティクス = 意味論
- リソースが持つ意味を確定させる
- 人間が読んで解釈するだけでなくプログラムも解釈できるようにする
- 標準化済みのmicroformats(一部)
- hCalendar:イベント情報
- hCard:プロフィール情報
- rel-lisence:ライセンス情報
イベントとかよく使うものは標準化しておくとスクレイピングなどするときに統一感があって楽になるって感じなのかな。 標準化されててもそれを使うか独自実装するかは個々人の勝手やけど、標準化されたものに従えばWeb世界は整理された綺麗なところになる。 ここらへんは知ってるか知らないかで大きく変わるからいろいろ知っておきたい。
12章 Atom
- Atomの目的 = RSSの標準仕様策定
- AtomはRSS(ブログ)だけでなく検索エンジンや写真管理など様々使える
- Atomのリソースモデル
- Atomの拡張
- Atom Threading Extensions
- Atom License Extension
- Feed Paging and Archiving
- OpenSearch
RSSとかでAtomって文字は見たことあったけど、今回初めてきちんと勉強した。 拡張もいろいろされていて、広く使われているんだろうなーという印象。 便利やとは思うけどどういう時に適しているかまではわからなかったので、一回自分で書くなりして触ってみたいなと感じた。
13章 Atom Publishing Protocol
AtomのエントリーをGETとかPOSTとかできるよーぐらいの理解しかしてない。 ブログサービスとかはこういう風にして設計・実装されてるのかなーとか思った。
14章 JSON
- JSON = JavaScript Object Notation
- データ型:オブジェクト、配列、文字列、数値、ブーリアン、null
- JSONPによるクロスドメイン通信
5部 Webサービスの設計
15章 読み取り専用のWebサービスの設計
- リソース設計
- リソース指向アーキテクチャ
郵便番号検索サービスの例をあげて説明されてたのでイメージしやすくわかりやすかった。 設計に関しては場数踏むしかないのかなとは思ってるので、本に書かれていたことを意識しながらいろいろ作っていきたいなと思った。
16章 書き込み可能なWebサービスの設計
- リソースの更新
- バルクアップデート
- パーシャルアップデート
- バッチ処理
- エラーなどの情報は207 Multi-Statusで表現
- トランザクション
- トランザクションリソースの利用
- 排他制御
- 悲観的ロック
- 楽観的ロック
バッチ処理、トランザクション、排他制御まで考えて実装したことがなかったので、大規模なサービスとかエラーが許されないサービスとかだといろいろ考えないといけないんだなと感じた。 また、これらを実現するために特別なプロトコルなどは利用しておらずHTTPのみで実現されているのは凄いなと思った。 ここらへんはある程度手法が確立されていてデザインパターンみたいになっているのかなと思うので、そこらへんも勉強していかなきゃなーと感じた。
17章 リソースの設計
設計手法は様々知っていると適切なカードが切れて将来便利かもしれないと思った。
AWSでマイクラ鯖たててみた٩( 'ω' )و
友達と遊ぶようにマイクラ鯖たてました。 とは言ってもそんなことのできるデスクトップPCは持ってないのでAWS使いました。
自宅鯖だとポート開放やら何やらあるけどAWSなので簡単に公開できました٩( ‘ω’ )و
公開するなら荒らし対策などやることはいろいろあるけど、今回は友達で遊べる最低限の設定まで。 (最低限やるべきことが出来てなかったら教えてください、、)
AWSインスタンスの生成
マイクラサーバーの設定
とりあえず必要なもののインストール。
$ sudo yum update -y $ java -version // java入ってるか確認。バージョンも確認。必要なら変更などする。 $ sudo yum -y install git
今回はspigotを使ってサーバー環境を整えていきます。 詳細まではわかってないけど、調べたところ公式よりもspigotが良いらしい。
$ mkdir ~/minecraft_spigot $ cd ~/minecraft_spigot $ wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar $ java -jar BuildTools.jar $ java -Xms1024M -Xmx1024M -XX:MaxPermSize=128M -jar spigot-1.11.2.jar nogui $ vi eula.txt eula=false -> true $ java -Xms1024M -Xmx1024M -XX:MaxPermSize=128M -jar spigot-1.11.2.jar nogui
クライアントからログインできるかテスト→ログインできたらstop
で一旦停止
起動/停止スクリプトの作成
いちいちコマンド打つのもあれなのでスクリプト作ります。
起動スクリプト(spi_start.sh)
#!/bin/bash USERNAME='ec2-user' SERVICE='spigot-1.11.2.jar' SCNAME='spigot' SPI_PATH='/home/ec2-user/minecraft_spigot/' XMX="1024M" XMS="1024M" cd $SPI_PATH ME=`whoami` if [ $ME == $USERNAME ] ; then if pgrep -u $USERNAME -f $SERVICE > /dev/null then echo "$SERVICE is already running!" else echo "Starting $SERVICE..." screen -AmdS $SCNAME java -Xmx$XMX -Xms$XMS -XX:MaxPermSize=128M -jar $SERVICE nogui fi else echo "Please run the minecraft user." fi
停止スクリプト(spi_stop.sh)
#!/bin/bash USERNAME='ec2-user' SERVICE='spigot-1.11.2.jar' SCNAME='spigot' SPI_PATH='/home/ec2-user/minecraft_spigot' cd $SPI_PATH ME=`whoami` if [ $ME == $USERNAME ] ; then if pgrep -u $USERNAME -f $SERVICE > /dev/null then echo "Stopping $SERVICE" screen -p 0 -S $SCNAME -X eval 'stuff "say SERVER SHUTTING DOWN IN 10 SECONDS. Saving map..."\015' screen -p 0 -S $SCNAME -X eval 'stuff "save-all"\015' sleep 10 screen -p 0 -S $SCNAME -X eval 'stuff "stop"\015' sleep 10 echo "Stopped minecraftserver" else echo "$SERVICE was not runnning." fi else echo "Please run the minecraft user." fi
権限の設定
$ chmod 744 spi_start.sh $ chmod 744 spi_stop.sh
ちゃんと動くか確認しておく
監視ツールの導入など
mackerelエージェントのインストール
サーバたてたらとりあえずmackerel入れとけ派の人なので入れます。 自分は何かあったらslackに飛ばすように設定してるので鯖落ちとかはすぐ気付けるようにしてます。
$ curl -fsSL https://mackerel.io/file/script/amznlinux/setup-all-yum.sh | MACKEREL_APIKEY='API_KEY' sh
定期的に再起動するように設定
ずっと起動しっぱなしは良くないので定期的に再起動するように設定します。
日本時間に変更
$ sudo vim /etc/sysconfig/clock ZONE="Asia/Tokyo" UTC=false $ cp /usr/share/zoneinfo/Japan /etc/localtime $ sudo /etc/init.d/crond restart
再起動スクリプト(spi_restart.sh)
#!/bin/bash
./spi_stop.sh; ./spi_start.sh
cronの設定
$ crontab -e 0 4 * * * /home/ec2-user/minecraft_spigot/spi_restart.sh | logger -t minecraft -p local0.info $ crontab -l $ sudo tail -f /var/log/messages // ログの保存先
参考HP
AWSで爆速でMinecraft Plugin鯖を建てる方法! - Qiita
さくらのVPSにspigotインストール(CentOS 7) | server-memo.net
Areas on the Cross-Section Diagram 解き方メモ
模式断面図の面積 | アルゴリズムとデータ構造 | Aizu Online Judgeを解く時に苦労したのでそのメモφ(・・
問題
模式断面図の面積 | アルゴリズムとデータ構造 | Aizu Online Judgeに書いてある通りです、ってのも素っ気ないので以下にスクショ貼ります。
図のような状況において、水たまりそれぞれの面積と全体の面積を出すという問題です。
入力として\
、_
、/
の列を受け取り、出力は全総面積、水たまりの数、各水たまりの面積になります。
上の図に対応する入力、出力は以下のようになります。 出力は総面積が35、水たまりの数が5、各水たまりの面積が4 2 1 19 9となっていることを示しています。
考え方
最初自力で考えてたんですが、全くわからなかったのでALDS1_3_D: Areas on the Cross-Section Diagram - State-of-the-Arsのコードを参考にしながら考えていきました。
コードを読んで参考になった考え方は、
\
の場所をスタックに保存することで、/
を受け取った時にそれと対応する\
を見つけることができる- 上記の方法で、対応する
\
、/
を用いて面積を計算すること = 横向きに面積を計算していくということ
アルゴリズムとしては、
- 入力が
\
のときはそのインデックスをスタックに保存する - 入力が
/
の時はスタックからpopし、現在のインデックスと組み合わせて横向きに面積を計算する - 計算した面積の総和をとって総面積を計算する
といった感じです。
ただ、これだけでは各水たまりの面積を計算できないので、
/
の時にpopしたインデックスと計算した面積のタプルを保存する- 必要に応じて上記のタプルを結合していく(水たまりを結合する操作に相当)
ということもします。
以上のために2つのスタックを用意します。
\
のインデックスを保存するスタック- popしたインデックスと計算した面積のタプルを保存するスタック
これだけだと意味不明なので例を出しながら説明します。
具体例で説明
具体例として、先ほどの問題の水たまりが1, 19となっている箇所を用いて説明します。
ちなみに入力は\/\\\\/_/\\///
となります。
今後はこの入力をインデックスと合わせて説明するので、簡単な表を書いておきます。
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
value | \ | / | \ | \ | \ | \ | / | _ | / | \ | \ | / | / | / |
index = 0
\
なのでスタック1にインデックスを保存します。
stack | stack2 |
---|---|
0 |
index = 1
/
なのでスタック1からpopします。
面積 = (現在のindex) - (popしたindex) = 1 - 0 = 1
となります。
popしたインデックスとこの計算結果をスタック2に保存します。
stack | stack2 |
---|---|
(0, 1) |
index = 2 ~ 5
\
らをスタック1に積んでいきます。
stack | stack2 |
---|---|
5 | |
4 | |
3 | |
2 | (0, 1) |
index = 6
/
なのでスタック1からpopします。
面積 = 6 - 5 = 1
となります。
popしたindexの5は、スタック2に保存したタプルに入っているindexである0より大きいので、そのままスタック2に保存します。
stack | stack2 |
---|---|
4 | |
3 | (5, 1) |
2 | (0, 1) |
スタック2が水たまりの様子を表しているので、これは面積1の水たまりが2つあることを示しています。 実際にここまで読み込んだ時の様子は以下のようになっています。
index = 7, 8
index = 7のとき_
なので特に何もしません。
index = 8のとき/
なので、スタック1からpopします。
面積 = 8 - 4 = 4
となります。
popしたindexである4はスタック2に積まれているタプルに保存されたindexである5より小さいので、水たまりは結合されます。
したがって、popしたindexである4と面積 = (タプルに保存されていた面積である) + (先ほど計算した面積) = 1 + 4 = 5
を合わせて保存します。
この時のスタックの状態は以下のようになります。
stack | stack2 |
---|---|
3 | (4, 5) |
2 | (0, 1) |
index = 9, 10
ちょっと面倒になってきたのでスタックの状態だけ、、
stack | stack2 |
---|---|
10 | |
9 | |
3 | (4, 5) |
2 | (0, 1) |
index = 11
stack | stack2 |
---|---|
9 | (10, 1) |
3 | (4, 5) |
2 | (0, 1) |
index = 12
stack | stack2 |
---|---|
(9, 4) | |
3 | (4, 5) |
2 | (0, 1) |
この時の状態はこんな感じ。
index = 13
/
なのでスタック1から3をpopする。
面積 = 13 - 3 = 10
となる。
popしたindexである3に対して、スタック2にある(4, 5), (9, 4)はindexの値が大きいのでこれらを結合する。
stack | stack2 |
---|---|
(3, 19) | |
2 | (0, 1) |
図的にはこんな感じ。
結果
スタックの状態は以下のとおり。
したがって、スタック2を見ると、面積1の水たまりと、面積19の水たまりがあることがわかる。
stack | stack2 |
---|---|
(3, 19) | |
2 | (0, 1) |
コード
rubyで書きました。
コードは以下の通り。まだ最適化できそうやけどとりあえずはこれで解けます。
sum = 0 stack = [] # \を受け取ったindexのstack res = [] # /に対応する\のindexとその時に計算した面積のtupleのstack. 最後に各断面の面積として表示する gets.split("").each_with_index { |v, i| if v == "\\" stack << i elsif v == "/" j = stack.pop if !j.nil? # \より先に/が来たときのため a = i - j # indexの引き算で横向きに計算する sum += a loop do k, b = res.pop if !k.nil? if j < k a += b else res << [k, b] # 一旦popしてるので特に何もなければ再度pushして戻す break end else break end end res << [j, a] end end } puts sum if res.size() == 0 puts "0" else puts "#{res.size()} #{res.map{|n| n[1]}.join(" ")}" # size == 0の時にmapがうまくいかない?? end
githubにもあげてます。
最後に
今回は最初さっぱりわからず30分くらい問題とにらめっこしてたのですが、横向きに面積を計算する、その時面積はインデックスの引き算で求めることができる、ということに気づくと後はすぐに解けました。
見方を変えれば簡単に解けるけど、その見方に気付けないという典型的な問題でした。
こういう見方は普段から引き出しを増やすしかないのかなーと思うので今後も勉強を続けていきたいなーと思いました。
ruby+opencvで画像処理(2値化)
画像処理の授業取っててruby+opencvで実装したのでその時のことをメモメモφ(・・
opencvといえばc++かpythonってイメージがあるけど、最近勉強中のrubyで実装したいなーと思ったのでしてみた。
開発環境
rubyでopencvを使うためのgemがopencv3系に対応していないのでこんな感じ。
インストール関係で叩いたコマンドは以下の通り。 地味にハマったのはopencv3も入っているからオプションでopencv2の方を見るようにしないといけなかったってところ。
$ brew install opencv $ gem install ruby-opencv -- --with-opencv-dir=/usr/local/Cellar/opencv/2.4.13.2
2値化とは
画像を白(1)か黒(0)の2つの値のみにすること。 色の情報が必要では無く形に着目するときとかに使うと良い。 また、情報量を減らせるので計算の面でもシンプル&高速になるとかなんとか。
現実世界やと、駐車場でナンバープレート見て清算済みなら勝手に開けてくれるとかあるけど、ああいうときに使われているらしい。
(雑な理解やけど多分だいたいあってる)
2値化の仕方
さて、どうやって2値化するかって話やけど、中身はすごい簡単。 各ピクセルの濃度値が閾値より大きければ黒、小さければ白にするだけ。 自分で実装してもそんなに大変ではないけど、opencvには閾値を渡せば2値化してくれるメソッドがあるので凄い簡単。
2値化するところは以下の通り。
カラーの画像をグレースケールにしてから、threshold()
を呼べば終わり。
正直引数については詳しく調べてなくて、255は8bitカラーの最大値からきててとりあえずこう書いとけばいいという雑な理解。
def binarize(file, t) image = CvMat.load(file) gray_img = image.BGR2GRAY bin_img = gray_img.threshold(t.to_i, 255, :binary) end
2値化は簡単にできるけど、問題になるのはその閾値をどうやって決めるかという話になって、それはいくつかアルゴリズムがあるらしい。
授業で出てきたのは、
の4つ。
微分ヒストグラム法
微分という名前の通り、濃度値の変化が大きいところに着目して閾値を決める方法。
ヒストグラム法やからヒストグラムを作るんやけど、どういうものかというと横軸が濃度値、縦軸が微分値の和になるようなヒストグラム。
多分普通は縦軸が単に画素数になるから、ここでは微分値を重みとしてちょっと変化させてるという感じ。
微分値の定義としては、| (基準となる画素の濃度値) - (隣の画素の濃度値) |
を周辺8マス分計算してその総和を取る。
ヒストグラムを作るところは以下のように実装した。
def create_diff_histogram(gray_img) hist = Array.new(256, 0) [*0...gray_img.rows].product([*0...gray_img.cols]).each{ |(y, x)| b = gray_img.at(y, x) diff = 0 # 周辺8マスとの差分の絶対値の総和の出す. # 自分自身との差は0になって結果に影響ないので9マス分計算している. [*y-1..y+1].product([*x-1..x+1]).each{ |(y2, x2)| if y2.between?(0, gray_img.rows-1) && x2.between?(0, gray_img.cols-1) diff += (b[0] - gray_img.at(y2, x2)[0]).abs end } hist[b[0]] += diff } hist end
全てのピクセルを網羅するためにx座標とy座標の直積を取る必要があって、それは[*0...gray_img.rows].product([*0...gray_img.cols]).each
と書いた。
product()
を使うと直積を取ってくれるのだが、配列で渡す必要があるので[*0...gray_img.rows]
としてrangeではなく配列にした。
個人的にはこの行がなんかもうちょっといい感じに書けないのかなーと思ってるけど、書き方がわからないのでこのまま。
各座標における色はat(y, x)
で取れる。
このat(y, x)
の返り値はCvScalar
型になってて、カラーだとRGB値、グレースケールだと輝度値が入っている。
CvScalar
は4つのdoubleを持つ配列になっているので、b[0]
のような書き方になる。
ちなみに、カラーのときはBGRの順に値を持っているので注意が必要。
あと、個人的に引っかかった点は引数が(x, y)でなく(y, x)だったこと。 最初何も考えずに(x, y)で書いていたのでout of rangeでエラー吐きまくってた。
微分値を計算するところは、周辺8マスを計算するためにまた直積を書いて実装している。 この書き方やと縦横3×3の9マスを網羅してて基準になるマスも入るけど、計算結果は変わらないので9マス分計算している。 あとは、端っこのピクセルやと隣がいない場合があるのでそのためのチェックもしてる。
普通なら2重ループになる直積が一行で書けるのはいいなーと思うけど、なんとなく読みづらいのが直したいポイント.
判別分析法
判別分析法は別名大津の2値化ともいうらしい。
ある値を閾値にしたときの分離度なる値を算出してそれが最大になるような閾値を採用するというアルゴリズム。 分離度は、閾値を元にそれより暗い/明るいの2つのグループに分けて、それぞれのグループでの分散を求めて、クラス内分散やクラス間分散を定義して、って感じで定義される。 この分離度だが、今回はその最大値を求めるという点で大小関係の比較ができれば良いので、結局各グループ内の画素数と濃度値の平均があれば十分になる。
数式の詳しい説明はちょっと長くなるのでこちら。
実装としては、閾値をある値にした時の分離度を求めて、それが最大になる閾値を返すというメソッドを作るだけなので簡単で、以下の通り。
def discriminant(file) image = CvMat.load(file) gray_img = image.BGR2GRAY hist = create_histogram(gray_img) eval_value, best_t = 0, 0 (0...255).each{ |t| # t := 閾値 # w1(w2) := 左側(右側)の画素数 # m1(m2) := 左側(右側)の平均 w1, w2 = 0, 0 m1, m2 = 0, 0 w1 = hist[0..t].reduce(:+) m1 = hist[0..t].map.with_index {|n, idx| n * idx}.reduce(:+)/w1 rescue 0 w2 = hist[t+1..255].reduce(:+) m2 = hist[t+1..255].map.with_index(t+1) {|n, idx| n * idx}.reduce(:+)/w2 rescue 0 e = w1 * w2 * (m1 - m2) ** 2 if eval_value < e eval_value = e best_t = t end } best_t end
create_histogram()
は横軸が濃度値、縦軸が画素数のシンプルなヒストグラムを返すように実装したメソッド。
indexが濃度値、値が画素数の配列の形でデータを保持している。
画素数は単に配列の値を足していけば良いだけなのでreduce(:+)
するだけ。
平均値は、画素数×濃度値を計算するためにindexと値が両方なので、map.with_index{|n, idx| n * idx}
のようにしてまず画素数×濃度値を計算した。
そのあとは同じくreduce(:+)
で総和を求めて、画素数w
で割って終わり。
画素数w
は0になることがあるのでZeroDivisionErrorの時のためにrescue 0
を書いている。
ただ、この書き方をすると他のエラーを握りつぶしてしまうのでもしかしたらよくないかも。
あと工夫しているところは閾値より大きいところを見るときにhist[t+1..255].map.with_index(t+1)
という書き方をしているところ。
配列の後ろ側を切り取ってからmapするときに、もとの配列でのindexを使いたい場合はwith_index(t+1)
とすることでindexを任意の値から数えることができる。
実行結果
画像処理定番のlenna使ってみました。 lennaだと2つの手法の差があまり出なくて向き不向きみたいなのが見えなくて残念やけど。
元画像
グレースケールにした画像
判別分析法を使って2値化した画像(閾値=122)
最後に
一応プログラムはgithubにあげてます。
今後も授業でいろいろ実装していく予定なので適宜まとめていこうかなと思ってます。
2017年2月の振り返り
2月も終わりなので今月の振り返り。
研究
就活に時間割いたので基本的に何もしてない。
M1の進捗とM2の予定をプレゼンしなきゃいけなかったのでそれの作成と発表しかしてないですね。
3月もする予定は特に無いですw
技術
前半はVPS上でdockerいろいろ触ってました。
インフラエンジニアで就活してたのでさすがに知らなさすぎてやばいなと思って勉強してたって感じ。
その流れでgoに入門したりもしてました。
後半はruby、railsの勉強をしてました。
これはインターン先がrailsだったから勉強してました。
js周りの勉強もその流れでしてました。
基本的には就活に向けてってのが多かったけど、インフラからフロントまで一通り触った1ヶ月だったなーという感想。
今年に入って広く浅くやってる感じになってるから、そろそろどれかに決めて深く勉強したいなーと思ってます。
技術アウトプット
前半はしっかりインプットもアウトプットもできたけど、後半はひたすらインプットしてました。
アウトプットの意識忘れないようにしていかないと。
就活
今月は選考が進んでて面接があったりインターンがあったりという感じ。
その中で人に会う度にそういう考え方もあるのかーと思ってました。
そのおかげで自分の強みや弱み、どういうことに興味があるのか、何をしたいのかとかを深掘りできたから良かったと思う。
院生の就活って推薦使ったり一般でも受ける企業が少なかったりするけど、自分が将来何をしたいのかが不明確なのであれば足を動かしていろんな人に会うと良いと感じました。
適当に選んで数年で転職とかはもったいないと思うし、楽しく仕事するためにしっかりこの機会に頭使わないとと思いました。
A Tour of GoでGoに入門した
Goの勉強にはまずA Tour of Go読むと良いっぽかったので読みました。
途中途中に練習問題があって、それを解きながら進めると適度に手も動かせて良かったです。
以下は自分用のまとめです。
最後に練習問題の解答をまとめて載せています。
(合ってるかは保証できません)
Packages, variables, and functions
- importはまとめて書くのがGo流
import ( "fmt" "math" )
- 大文字から始めると他から参照可能。小文字からだと外からはアクセスできない。
- 関数の宣言は"func 関数名 (変数名 型, 変数名 型) 返り値の型"。型が同じ時はまとめてok。返り値は複数でもok。
func add(x int, y int) int { return x + y } func add(x, y int) int { return x + y } func swap(x, y string) (string, string) { return y, x }
- 返り値にあらかじめ名前をつけておくこともできる。その場合returnだけでよし。
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return }
- "var 変数名 型"で変数宣言。初期値を与えないときはゼロ値が入る。
- 初期化するときは"変数名 := 値"で宣言と初期化をまとめてできる。型は推論される。
var i, j int k := 3
- 型変換は"型名(値)"で可能。
- constキーワードを用いると定数を宣言可能。定数の時は":="でなく"="。
Flow control statements: for, if, else, switch and defer
- For文は括弧無しversion。式の省略も自由
for i := 0; i < 10; i++ { sum += i } for ; sum < 1000; { sum += sum }
- While文はないのでFor文で表現
// while文 for ; sum < 1000; { sum += sum } // 無限ループ for { // do something }
- If文も括弧無しverison。
if x < 0 { // do something }
- 特殊なのは簡単な式をIf文の中で書ける。スコープはIf文内のみ。
if v := math.Pow(x, n); v < lim { return v }
- Switch文はcaseで強制break。続けるときは"fallthrough"をcaseの最後に書く。
- Deferを使うと呼び出し元の関数がreturnする時まで評価を遅らせる。(引数は評価されるがその関数は評価されない)
- 複数deferがあるときはLIFOの順で処理される。
More types: structs, slices, and maps.
- ポインタが使える。&でポインタを取得、*でポインタが示す先の値を取得。
func main() { i, j := 42, 2701 p := &i // point to i fmt.Println(*p) // read i through the pointer *p = 21 // set i through the pointer fmt.Println(i) // see the new value of i p = &j // point to j *p = *p / 37 // divide j through the pointer fmt.Println(j) // see the new value of j }
- 構造体もある。"type 構造体名 struct"の形で宣言。
- 構造体へはドットでアクセス。
type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v.X) }
- ポインタを用いて構造体へアクセスもできる。このとき(*p).Xと書かなくても勝手に解釈してくれる。
func main() { v := Vertex{1, 2} p := &v p.X = 1e9 fmt.Println(v) }
- 配列は"var 変数名 [サイズ]型"で宣言。配列の長さは後から変えれない。
var a [10]int
- 配列を便利に扱うためにsliceがある。
primes := [6]int{2, 3, 5, 7, 11, 13} var s []int = primes[1:4] fmt.Println(s) // [3 5 7]
- sliceは元の配列の部分を参照しているだけ。したがって、sliceの値を変えると元の配列の値も変わる。
- sliceを重ねて新たなsliceを作ることができる。
s := []int{2, 3, 5, 7, 11, 13} s = s[1:4] fmt.Println(s) // [3 5 7] s = s[:2] fmt.Println(s) // [3 5] s = s[1:] fmt.Println(s) // [5]
- sliceはlengthをcapacityを持っている。lengthはsliceに含まれる要素数。capacityはsliceの最初の要素から数えて元の配列の最後の要素までの数。
- lengthとcapacityはlen(s)とcap(s)の形で取得可能。
- make関数を用いてsliceを作成。make関数はarrayを作成しそのsliceを返す。
a := make([]int, 5) // len(a)=5 b := make([]int, 0, 5) // len(b)=0, cap(b)=5
- append関数を用いてsliceに要素を追加することができる。capacityを超えたとき、より大きなサイズの配列を割り当て直す。
var s []int s = append(s, 0) s = append(s, 1)
- rangeを使えば反復処理ができる。rangeを使うと2つの値が返され、1つ目がindex、2つ目がその要素の値。
- 使わない時は"_"で捨てることができる。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } for _, value := range pow { fmt.Printf("%d\n", value) }
- Mapを用いてキーと値を関連づけることができる。
- make関数を用いてmapを作成することで要素の追加などが可能。
m = make(map[string]Vertex) m["Bell Labs"] = Vertex{ 40.68433, -74.39967, }
- "elem, ok = m[key]"の形でキーに対する要素が存在するかを確認できる。キーがあれば"ok = true"、なければ"ok = false"になる。
m := make(map[string]int) m["Answer"] = 42 // 初期化 fmt.Println("The value:", m["Answer"]) m["Answer"] = 48 // 更新 fmt.Println("The value:", m["Answer"]) delete(m, "Answer") // 削除 fmt.Println("The value:", m["Answer"]) v, ok := m["Answer"] // 取得 fmt.Println("The value:", v, "Present?", ok)
- 無名関数を渡すことも可能。
hypot := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(5, 12))
Methods and interfaces
- Goにクラスはないが、レシーバ引数を用いてっぽいことができる。レシーバ引数を使う時は"func (変数名 型) 関数名 (引数名 型) 返り値の型 "の形で使う。
- レシーバ引数を使うとドットを用いてアクセス可能。
- レシーバを伴うメソッドは同じパッケージ内でする必要がある。
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
- ポインタレシーバを用いて関数を宣言することも可能。レシーバの値を更新する時はこちらで。
func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f }
- ポインタレシーバの時は値でもポインタでもどちらを渡してもうまくやってくれる。
var v Vertex v.Scale(5) // OK p := &v p.Scale(10) // OK
- インターフェースの宣言もできる。明示的に書かなくても実装すればok。
type I interface { M() } type T struct { S string } func (t T) M() { fmt.Println(t.S) }
func (t *T) M() { if t == nil { fmt.Println("<nil>") return } fmt.Println(t.S) }
- nilインターフェースの値は値も具体的な型ももたない。メソッドを呼び出すとランタイムエラーになる。
type I interface { M() } func main() { var i I describe(i) i.M() // ランタイムエラー }
- 型アサーションができる。"t, ok := i.(T)"の形で、iがTを保持してればtにその値が入り、okにtrueが入る。otherwise, t=ゼロ値でok=false。
var i interface{} = "hello" s, ok := i.(string) fmt.Println(s, ok)
- 型アサーションを使って型Switchができる。
switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) }
- Goではエラーをerror変数を用いて表す。エラー型は以下のインターフェース。
type error interface { Error() string }
Concurrency
- goroutineという並列化機構がある。"go f(x, y, z)"と書けば別スレッドで動かせる。
- チャネル型を用いて値のやりとりができる。
ch := make(chan int) // チャネルの作成 c <- sum // 値の送信 x := <-c // 値の受信
- チャネルはバッファとして使える
ch := make(chan int, 100) // 2つ目の引数でバッファのサイズを指定
- これ以上値を送信しないことを示すためにチャネルのcloseができる。受信側は2つ目の引数でcloseされているかを知ることができる。
close(ch) // チャネルを閉じる v, ok := <-ch // チャネルが閉じていればok=false
- selectを用いて複数のチャネルを待つことができる。defaultを用いれば待ち状態にはならずdefaultを実行する。
select { case <-tick: fmt.Println("tick.") case <-boom: fmt.Println("BOOM!") return default: fmt.Println(" .") time.Sleep(50 * time.Millisecond) }
- syncパッケージを用いて排他制御などをおこなうことができる。
type SafeCounter struct { v map[string]int mux sync.Mutex } func (c *SafeCounter) Inc(key string) { c.mux.Lock() c.v[key]++ c.mux.Unlock() }
Execises
- Loops and Functions
package main import ( "fmt" "math" ) func Sqrt(x float64) float64 { z := 1.0 for i := 0; ; i++ { diff := ( z * z - x ) / ( 2 * z ) if math.Abs(diff) < 1e-10 { fmt.Printf("loop count is %d\n", i) return z } z -= diff } } func main() { fmt.Println(Sqrt(2)) }
- Slices
package main import "golang.org/x/tour/pic" func Pic(dx, dy int) [][]uint8 { pic := make([][]uint8, dy) for i := range pic { pic[i] = make([]uint8, dx) } for x := 0; x < dx; x++ { for y := 0; y < dy; y++ { pic[x][y] = uint8(x^y) } } return pic } func main() { pic.Show(Pic) }
- Maps
package main import ( "golang.org/x/tour/wc" "strings" ) func WordCount(s string) map[string]int { m := make(map[string]int) for i := range strings.Fields(s) { m[strings.Fields(s)[i]] += 1 } return m } func main() { wc.Test(WordCount) }
- Fibonacci closure
package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { n1, n2 := 0, 1 return func() int { n1, n2 = n2, n1+n2 return n1 } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
- Stringers
package main import "fmt" type IPAddr [4]byte // TODO: Add a "String() string" method to IPAddr. func (ip IPAddr) String() string { return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3]) } func main() { hosts := map[string]IPAddr{ "loopback": {127, 0, 0, 1}, "googleDNS": {8, 8, 8, 8}, } for name, ip := range hosts { fmt.Printf("%v: %v\n", name, ip) } }
- Errors
package main import ( "fmt" ) type ErrNegativeSqrt float64 func (e ErrNegativeSqrt) Error() string { return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e)) } func Sqrt(x float64) (float64, error) { if x < 0.0 { return 0.0, ErrNegativeSqrt(x) } z := 1.0 for i := 0; ; i++ { if z2 := z - (z*z-x)/(2*z); z2 - z < 1e-10 { return z, nil } else { z = z2 } } } func main() { fmt.Println(Sqrt(2)) fmt.Println(Sqrt(-2)) }
- Readers
package main import "golang.org/x/tour/reader" type MyReader struct{} func (r MyReader) Read(b []byte) (int, error) { b[0] = 'A' return 1, nil } func main() { reader.Validate(MyReader{}) }
- rot13Reader
package main import ( "io" "os" "strings" ) type rot13Reader struct { r io.Reader } func (reader *rot13Reader) Read(b []byte) (n int, err error) { n, err = reader.r.Read(b) // bに文字を読み込む // 以下で文字を変換する for i := range b { if ('A' <= b[i] && b[i] <= 'M') || ('a' <= b[i] && b[i] <= 'm') { b[i] += 13 } else if ('N' <= b[i] && b[i] <= 'Z') || ('n' <= b[i] && b[i] <= 'z') { b[i] -= 13 } } return n, err } func main() { s := strings.NewReader("Lbh penpxrq gur pbqr!") r := rot13Reader{s} io.Copy(os.Stdout, &r) }
- Images
package main import ( "golang.org/x/tour/pic" "image" "image/color" ) type Image struct{ w, h int } func (i Image) ColorModel() color.Model { return color.RGBAModel } func (i Image) Bounds() image.Rectangle { return image.Rect(0, 0, i.w, i.h) } func (i Image) At(x, y int) color.Color { return color.RGBA{uint8(x*y), uint8(x+y), 255, 255} } func main() { m := Image{100, 100} pic.ShowImage(m) }
- Equivalent Binary Trees
package main import ( "fmt" "golang.org/x/tour/tree" ) // Walk walks the tree t sending all values // from the tree to the channel ch. func Walk(t *tree.Tree, ch chan int) { if t == nil { return } Walk(t.Left, ch) ch <- t.Value Walk(t.Right, ch) } // Same determines whether the trees // t1 and t2 contain the same values. func Same(t1, t2 *tree.Tree) bool { c1, c2 := make(chan int), make(chan int) go func() { Walk(t1, c1) close(c1) }() go func() { Walk(t2, c2) close(c2) }() for { x, ok1 := <-c1 y, ok2 := <-c2 if !ok1 || !ok2 { return true } if x != y { return false } } return true } func main() { ch := make(chan int) t := tree.New(1) go func() { Walk(t, ch) close(ch) }() for v := range ch { fmt.Println(v) } if Same(tree.New(1), tree.New(1)) { fmt.Println("Same") } if !Same(tree.New(1), tree.New(2)) { fmt.Println("Not Same") } }
- Web Crawler
package main import ( "fmt" "sync" ) type Fetcher interface { // Fetch returns the body of URL and // a slice of URLs found on that page. Fetch(url string) (body string, urls []string, err error) } func Crawl(url string, depth int, fetcher Fetcher) { m := make(map[string]bool) crawl(url, depth, fetcher, m) } // Crawl uses fetcher to recursively crawl // pages starting with url, to a maximum of depth. func crawl(url string, depth int, fetcher Fetcher, m map[string]bool) { if depth <= 0 { return } mux := &sync.Mutex{} mux.Lock() if v, ok := m[url]; v && ok { return } m[url] = true mux.Unlock() body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) wg := &sync.WaitGroup{} for _, u := range urls { wg.Add(1) go func (u string) { crawl(u, depth-1, fetcher, m) wg.Done() }(u) } wg.Wait() return } func main() { Crawl("http://golang.org/", 4, fetcher) } // fakeFetcher is Fetcher that returns canned results. type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := f[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } // fetcher is a populated fakeFetcher. var fetcher = fakeFetcher{ "http://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "http://golang.org/pkg/", "http://golang.org/cmd/", }, }, "http://golang.org/pkg/": &fakeResult{ "Packages", []string{ "http://golang.org/", "http://golang.org/cmd/", "http://golang.org/pkg/fmt/", "http://golang.org/pkg/os/", }, }, "http://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, "http://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, }
docker、jenkinsの導入
今回やること
- dockerの導入
- jenkinsの導入
サーバ環境
手持ちのさくらVPS上で環境作ります。
$ cat /proc/version Linux version 3.13.0-108-generic (buildd@lgw01-60) (gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) ) #155-Ubuntu SMP Wed Jan 11 16:58:52 UTC 2017 $ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=14.04 DISTRIB_CODENAME=trusty DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"
dockerの導入
公式サイト(Get Docker for Ubuntu - Docker)を参考に導入しました。
随時更新されていると思うので最新の情報を参照した方が良いと思います。
- curlをいれておくことをオススメされたのでいれる
$ sudo apt-get update $ sudo apt-get install curl \ linux-image-extra-$(uname -r) \ linux-image-extra-virtual
- 公式サイト通りにインストールを進める
$ sudo apt-get install apt-transport-https \ ca-certificates $ curl -fsSL https://yum.dockerproject.org/gpg | sudo apt-key add - $ apt-key fingerprint 58118E89F3A912897C070ADBF76221572C52609D $ sudo apt-get install software-properties-common $ sudo add-apt-repository \ "deb https://apt.dockerproject.org/repo/ \ ubuntu-$(lsb_release -cs) \ main" $ sudo apt-get update $ sudo apt-get -y install docker-engine
- 動作確認
$ sudo docker run hello-world
表示を見た感じちゃんと動いていれば大丈夫だと思います。
- バージョン確認
$ docker version Client: Version: 1.13.0 API version: 1.25 Go version: go1.7.3 Git commit: 49bf474 Built: Tue Jan 17 09:50:17 2017 OS/Arch: linux/amd64
というわけで1.13.0が入ったみたいです。
ちなみにインストールする際にバージョンを指定することも可能です。
インストール可能なバージョンは以下のコマンドで確認できます。
$ apt-cache madison docker-engine
- ユーザグループの作成
sudo無しで実行できるようにdockerグループを作成します。
僕の場合グループは既に存在していたので、ユーザを追加するだけでした。
$ sudo groupadd docker groupadd: group 'docker' already exists $ sudo usermod -aG docker $USER
1回ログインし直して設定を適用します。
sudo無しで実行できればokです。
$ docker run hello-world
jenkinsの導入
公式のイメージがあるのでそれを使います。
https://hub.docker.com/_/jenkins/
$ docker search jenkins NAME DESCRIPTION STARS OFFICIAL AUTOMATED jenkins Official Jenkins Docker image 2475 [OK] $ docker run -p 8080:8080 jenkins
- コンテナの状態を確認します。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a15377c1714f jenkins "/bin/tini -- /usr..." 24 minutes ago Up 24 minutes 0.0.0.0:8080->8080/tcp, 50000/tcp pensive_brahmagupta
- ブラウザからjenkinsをひらきます。(IP_ADDRESS:8080)
docker runコマンドの実行時にパスワードが表示されているのでそちらを入力します。
その後pluginはオススメを選び、ユーザの作成など順序通りに進みます。
dockerの使い方
docker psコマンドを用いてidを取得します。
その後docker stop、docker startコマンドで停止、起動をおこないます。
コンテナの削除はdocker rmコマンドでできます。
$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a15377c1714f jenkins "/bin/tini -- /usr..." About an hour ago Up 7 seconds 0.0.0.0:8080->8080/tcp, 50000/tcp pensive_brahmagupta $ docker stop pensive_brahmagupta $ docker start pensive_brahmagupta $ docker stop pensive_brahmagupta # rmの前はstopしなければならない $ docker rm pensive_brahmagupta
さいごに
dockerとjenkinsの環境構築ができました。
次回はjenkinsの設定をしてテストの自動化などをしていきたいと思います。