NSQって何?

Go言語によるWebアプリケーション開発という書籍(O’REILLY)を購入して勉強中の初心者Gopherです。
この5章にある「分散システムと柔軟なデータの処理」をやってみたのですが、
この出版社の日本語訳がわかりずらい。さらに文書中にコードが散在していて拾うの結構大変(main.goに書くのをtwitter.goに書いたり)。
さらに、著者のサイトにサンプルコードもあるのですが、実際に書籍に書いてある内容とソースが違っている(たぶん改訂したのか?)
と様々問題はあるのですが、自分なりに整理をして、もし、同じようなところで「勉強しずらいなー」と感じている人向けに共有します。
こういうのってQiitaでやったほうがいいのか判断がつかないので自ブログで。

まずこの章を乗り越える前提に必要なNSQから。

NSQとは

メッセージキューの一種(あるプログラムから別のプログラムへメッセージとイベントを送信できる。)
送信先のプログラムは複数あっても、別のマシン上のものでも構わない。

■当方の実行環境

Mac OS X High Sierra
nspd v1.2.0
nslookupd v.1.2.0
go version go1.11.1 darwin/amd64
Homebrewをインストール済

■インストール

Macは下記でOK

$ brew install nsq

その他OSは
https://nsq.io/deployment/installing.htmlを参照

GOPATHの直下に移動して
Goで利用するには別途ドライバーをインスールする。

$ go get github.com/bitly/go-nsq

参考URL https://postd.cc/dissecting-message-queues/

NSQはBitlyが構築したメッセージングプラットフォーム。
プラットフォームと言ったのは、リアルタイムの分散型メッセージングに、
より役立つよう構築されたツールがNSQ周辺にはたくさんある。

◾️デーモンの起動

起動はそれぞれにターミナルを開いて実行する。
終了はCtrl+Cで。

$ nsqd

メッセージを受信し、格納し、クライアントに送り届けるデーモン。
デーモンはスタンドアロンでも動作する。
NSQは分散型トポロジとして動作するようデザインされている。

$ nsqlookupd

nsqlookupdはnsqdインスタンスのためのサービスを検出する仕組み。
ディフォルトでバインドするアドレス、ポート
TCPは 0.0.0.0:4160
HTTPは 0.0.0.0:4161

管理コマンド

$ nsqadmin --lookupd-http-address=127.0.0.1:4161

リアルタイムのクラスタ統計を表示し、
キューの削除やトピックの処理など様々な管理業務を実行する役割を果たすWeb UIを提供。

■動作確認
メッセージを発行する。

$ curl -d 'hello world 1' 'http://127.0.0.1:4151/pub?topic=test'

別のターミナルで, nsq_to_file コマンドを実行

$ nsq_to_file --topic=test --output-dir=/tmp --lookupd-http-address=127.0.0.1:4161

test.*.logファイルが /tmp 以下に書き込まれ、「hello world 1」という文字列があればOK。

とりあえず、ここまで!

引用
NSQ 公式ページ QuickStart

CentOSの設定-ミドルウェアのバージョン隠蔽とhosts.allowファイル

今日もGolangの記事を投稿したかったのですが、急遽サーバー周りの設定をいじることになりましたので
そのTipsを参考までに投稿します。(サーバー管理は自己責任でやってください)

sshでの接続許可の設定なのですが、お客様都合でしばらくの間固定IPでの運用ができない状況に。。。
rsaの秘密鍵、公開鍵方式でsshするのが常套手段なのですが、
お客様側でそうした経験もないらしく。。。
さらにこのサーバは年明けそうそうにお役目を終了予定なので、あまり突っ込んだ設定も避けたいという事情も。

固定IPのみの接続を許可していたのであまり気にかけていなかったのですが、
いくつか気になった箇所もあり、

  1. SMTP postfixの設定変更
  2. response headerのApacheバージョンを隠蔽する
  3. response headerのPHPバージョンを隠蔽する
  4. hosts.allowの設定を変更する

という対策をしておきました。
後はfail2banのインストールなどでsshの不正アクセスを防げばしばらくは安全?。

固定IPが振り出され時点でhosts.allowを書き換えるので、それまでの一時しのぎですが。
少々確認に手間取ったのでメモ書き程度ですが参考になればと思います。

postfixの設定変更

今回のメールサーバーは送信のみ(送信専用)しか利用していない。
postfixの設定を確認する。

# vi /etc/postfix/main.cf

上記のコマンド(Viエディタ)でpostfixの設定ファイルを開く。

# SMTPサーバーの情報を表示しない
smtpd_banner = ESMTP unknown
# ユーザーの存在問い合わせに答えないようにする。
disable_vrfy_command = yes

を追記する。
編集が終わったら:wqして保存。
postfixをreloadする。今回触ったのはCentOS6.xなので下記コマンド。

# /etc/init.d/postfix reload

レスポンスヘッダのApacheバージョンの隠蔽

Apache2.4の設定
ソースコンパイルしてあったので、ディレクトリがyumでインストールした場合と異なる。

# vi /usr/local/apache2/conf/httpd.conf

httpd.conf内ではhttpd-default.confをコメントアウトし利用していないので
直接httpd.confへ追記した。(480行目付近)

# Various default settings
#Include conf/extra/httpd-default.conf
ServerTokens ProductOnly
ServerSignature Off
TraceEnable Off

Apacheはソースからコンパイルしたので下記で再起動する。

# /usr/local/apache2/bin/apachectl restart

yumからインストールした場合(CnetoOS6)は下記(今となってはなつかしの)コマンドで再起動。

# /etc/init.d/httpd restart

レスポンスヘッダのPHPバージョンの隠蔽

この点とっかかりに攻撃してくる可能性は大いにあります。
通常yumでインストールすると/etc/php.iniに設定ファイルがあります。
phpもソースコンパイルしたため、当方の環境では/usr/lib64/php.iniにファイルがありました。
各自の環境にあわせて確認してください。

vi /usr/lib64/php.ini

でファイルを開いて

expose_php = Off

とします。:wqでviを閉じます。
apacheの再読込か再起動をして設定を反映してください。

hosts.allowの設定を変更する

サーバーへアクセスしている事業所が移転のため固定IPでなくなった。
基本はhosts.denyでALL:ALLで全部拒否する。
hosts.allowで必要なIPまたはドメインを指定する。サーバーの再起動は特に必要なしです。
数年前に はじめて、このファイルを扱う際は指定方法がわからず。右往左往しました。

今回は国内のみのIPを許可し、海外のIPは受け付けないようにしました。

ALL : 127.0.0.1
#本来は固定IPのみ許可したほうが良い
sshd : .jp
vsftpd : .jp

192.168.0.とか途中で記述する方法
ドメインも.com .netとかで指定する方法
などいくつか指定方法はあります。
複数指定する場合は区切り文字は半角スペースにする。

設定に失敗すると自身もログインできなくなる場合もあります。作業時は必ずコンソールを2以上接続し、設定後のsshの接続テストを行なうようご注意ください。

GolangでRestAPIを作る-軽量フレームワークgorilla利用

Go初心者(A Tour of Go を2週目くらい)の私ですが、
そろそろ何か作ってみないと実力がつかないと思い、できるだけ短時間で学べる学習素材が無いか探してみました。ありました勉強できそうな動画がyoutubeに!(Golang REST API With Mux)英語ですがナレーションあり。ただし日本語字幕はありません。
実際コードを書く手順が目で追えますので多少英語のヒアリングできなくても心配ありません。
便利ですね。youtube.

近頃はyoutubeで参考になる動画が増えてきているようです。
参考にしたサイトは下段の関連リンクへまとめましたので、ご興味ある方は是非本記事とあわせてご確認いただければ幸いです。
職種は問わず、プログラミングに興味を持ち日々プログラミング言語学習に取り組まれている方へ、できるだけ手短にわかりやすくまとめてみました。
(結局は長くなりそう。)

動作環境準備

開発するためには最低でもPC1台は必要です。MAC、Windouwsまたはlinuxの各ディストリビューションのいずれかで構いません。

Goのインストール

Goのインストールについては各OS別にバイナリ版でインストールするか、ソースからコンパイルする方法等いくつかあります。
詳細についてはGoの公式サイトでご確認ください。
私の環境はMAC OS XのHigh Sierra(10.13.6)で少々古いです。
GoのインストールはHomebrewを使い行いました。
Homebrewがインストール済なら下記のコマンドをターミナルから実行すればOKです。

$ brew install go

Mac環境に関しては下記のURLに簡潔にまとめられておりましたので、もしインストールに困ったら参考に。
初めてのGo!インストールまで(qiita)
今後どこかのタイミングでGolang環境構築については別記事にまとめたいと思います。

Gopathとディレクトリ(参考例)

$GOPATH = /home/[UserName]/go
$GOPATH/bin
     |
     +-/pkg
     |
     +-/src
          |
          +-/github.com
                 |
                 +-- /gorilla  下記のgo getでパッケージのインストールをするとここへgorillaが配置されます。
                 |        |
                 |        +-- /mux
                 |        |
                 |        +-- /websoket
                 |
                 +-- /[Github UserName]
                                |
                                +--/restapi
                                       このディレクトリにmain.goファイルを作成しソースを書く

パッケージGorillaのインストール

gorillaはGoのウェブ開発用ツールキットです。
ツール毎にリポジトリがわかれている。
今回練習で実装するRestApiでは
ルーティング機能を使います。
インストールはターミナルから下記のgoコマンドを実行します。
GOPATHの直下に移動して実行するといい感じにパッケージがインストールされるはず。

$ go get -u github.com/gorilla/mux

コードについての説明

設計

作るものRestAPI
特定のURIにアクセスするとJson形式の情報がbodyに出力される。
実際はフロントエンド側でアプリを別途作成し、アプリ側からこのAPIをコール。
扱う情報は書籍(本)の情報とし、
項目は
 ID, ISBN, TITLE, AUTHOR
 AuthorはFirstname, Lastnameで構成する。

実際はデータベース等の接続と更新処理を必要とするが、これは練習なので
データベース接続や処理はモックとする。

APIのMETHODとエンドポイントは以下の構成とする。

http://localhost:8000/api/books
 期待値:複数の書籍情報を返す。
 メソッド GET
 パラメータ なし
 戻り値
 DBから取得した複数の書籍情報を返す。

http://localhost:8000/api/books/[id]
 期待値:IDに一致した書籍情報を返す。
 メソッド GET
 パラメータ ID
 戻り値:該当があれば1冊分の書籍情報を返す。

http://localhost:8000/api/books
 期待値:1冊の書籍情報を追加する
 メソッド POST
 パラメータ なし
 戻り値:追加後の1冊分のデータ
 POSTデータ:ISBN, TITLE, AUTHOR

http://localhost:8000/api/books/[id]
 期待値:1冊の書籍情報の更新する
 メソッド PUT
 パラメータ ID
 戻り値:更新後の1冊分のデータ
 PUTデータ:ID, ISBN, TITLE, AUTHOR

http://localhost:8000/api/books/[id]
 期待値:1冊の書籍情報の削除する
 メソッド DELETE
 パラメータ ID
 戻り値:削除後残った全書籍データ

実装コード

動画の中で書きすすめられたコードを写経してみた。

package main

import(
  "encoding/json"
  "log"
  "net/http"
  "math/rand"
  "strconv"
  "github.com/gorilla/mux"
)

// Book Struct
type Book struct {
  ID	string	`json:"id"`
  Isbn	string	`json:"isbn"`
  Title	string	`json:"title"`
  Author	*Author	`json:"author"`
}

// Author Struct
type Author struct {
  Firstname	string	`json:"firstname"`
  Lastname	string	`json:"lastname"`
}

// Init books var as a slice Book struct
var books []Book 

// Get All Books
func getBooks(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  json.NewEncoder(w).Encode(books)
}

// Get Single Book
func getBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  params := mux.Vars(r) // Get params
  // Loop through books and find with id
  for _, item := range books {
    if item.ID == params["id"] {
      json.NewEncoder(w).Encode(item)
      return
    }
  }
  json.NewEncoder(w).Encode(&Book{})
}

// Create a new Book
func createBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  var book Book
  _ = json.NewDecoder(r.Body).Decode(&book)
  book.ID = strconv.Itoa(rand.Intn(10000000)) // Mock ID - not safe
  books = append(books, book)
  json.NewEncoder(w).Encode(book)
}

// Update a Book
func updateBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  params := mux.Vars(r)
  for index, item := range books {
    if item.ID == params["id"] {
      books = append(books[:index], books[index+1:]...)
      var book Book
      _ = json.NewDecoder(r.Body).Decode(&book)
      book.ID = params["id"]
      books = append(books, book)
      json.NewEncoder(w).Encode(book)		
      return
    }
  }
  json.NewEncoder(w).Encode(books)
}

// Delete a Book
func deleteBook(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  params := mux.Vars(r)
  for index, item := range books {
    if item.ID == params["id"] {
      books = append(books[:index], books[index+1:]...)
      break
    }
  }
  json.NewEncoder(w).Encode(books)
}

func main() {
  // init Router
  r := mux.NewRouter()

  // Mock Data - implemant DB
  books = append(books, Book{ID: "1", Isbn: "448743", Title: "Book One", Author: &Author{Firstname: "John", Lastname: "Doe"}})
  books = append(books, Book{ID: "2", Isbn: "847564", Title: "Book Two", Author: &Author{Firstname: "Steve", Lastname: "Smith"}})

  // Route Handler / Endpoints
  r.HandleFunc("/api/books", getBooks).Methods("GET")
  r.HandleFunc("/api/books/{id}", getBook).Methods("GET")
  r.HandleFunc("/api/books", createBook).Methods("POST")
  r.HandleFunc("/api/books/{id}", updateBook).Methods("PUT")
  r.HandleFunc("/api/books/{id}", deleteBook).Methods("DELETE")
  log.Fatal(http.ListenAndServe(":8000", r))

}

実行

とりあえず、簡略な方法としてターミナル内でrestapiの直下へ移動し

$ go build && ./restapi

テスト

restapi以下に作成したmain.goをビルド&実行した後に
各エンドポイント毎にjson形式でGET,POST,PUT,DELETEする。
Postman(アプリ)や「Talend API Tester – free edition」(chrome拡張機能)のツールを使う。
詳細は別記事でまとめます。

 

関連リンク

参考資料

Javascriptデザインパターン-シングルトンについて

長年プログラミングをしているわりに、
ちゃんとしたデザインパターンを整理していない自分に気付きました。
初回の記事は利用頻度の高いJavascriptのデザインパターンから共有したいと思います。
デザインパターンはコードを書く上で基本と申しますか設計上の基本・基礎。
参考文献、参考リンクは記事の末尾に記載しました。

newを使ってシングルトンを作る

Javascriptでシングルトンと言うと「モジュールパターン」として使われますが、それとは別な考察(newを使ってシングルトンを作る)が参考文献のほうにはありました。
このブログではそのうちの1例を自分で書いて実行・テストし確かめてみました。

function Singleton() {

    // キャッシュするインスタンスを格納する変数
    var instance;

    // コンストラクタ書き換える関数を定義する
    Singleton = function Singleton() {
        return instance;
    }

    // プロトタイププロパティを引き継ぐ
    Singleton.prototype = this;

    // インスタンス
    instance = new Singleton();

    // コンストラクタのポインタを再定義
    instance.constructor = Singleton;

    // 機能のすべて
    instance.start_time = 0;
    instance.bang = "hoge";

    return instance;
}

// 期待値のテスト
Singleton.prototype.nothing = true;
var singleton1 = new Singleton();
Singleton.prototype.everything = true;
var singleton2 = new Singleton();

if (singleton1===singleton2) {
    // 同じものを指す
    console.log("同じインスタンス!");
}
else
{
    console.log("違うインスタンス!");
}
// プロトタイププロパティは定義された時点ですぐに機能する
console.log(singleton1.nothing);
console.log(singleton1.everything);
console.log(singleton2.nothing);
console.log(singleton2.everything);
// 通常のプロパティ
console.log(singleton1.bang); // hoge
// コンストラクタのポインタも正しいところをみる
if (singleton1.constructor === Singleton) {
    console.log("ポインタOK");
} else {
    console.log("ポインタNG");
}

テスト

上記のコードをsingleton.jsというファイル名で保存しnode.jsで実行してみる。

$ node singleton.js

実行して以下が吐かれればOK
同じインスタンス!
true
true
true
true
hoge
ポインタOK

javascriptデザインパターン-シングルトン