『Go言語によるWebアプリケーション開発』学習メモ(2日目)

『Go言語によるWebアプリケーション開発』という本での学習の中で、 学んだことを自分用に整理してメモしていきます。

前回から大分時間が空いてしまったのですが、進めていきます。

www.oreilly.co.jp

第2章 認証機能の追加

ログインしたユーザー名の表示

func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t.once.Do(func() {
        t.templ =
            template.Must(template.ParseFiles(filepath.Join("templates",
                t.filename)))
    })
    data := map[string]interface{}{
        "Host": r.Host,
    }
    if authCookie, err := r.Cookie("auth"); err == nil {
        data["UserData"] = objx.MustFromBase64(authCookie.Value)
    }
    t.templ.Execute(w, data)
}
       <form id="chatbox">
            {{.UserData.name}}:<br/>
            <textarea></textarea>
            <input type="submit" value="送信" />
        </form>

実行の流れ

  1. http.Handlerのインターフェースを実装する。templateHandlerは、 HTTPリクエストを直接処理することが出来るようになる

  2. t.onece.Doで、テンプレートファイルを一回だけロードして実行している

  3. dataという、新しいマップを作成し、リクエストから取得したホスト名をHostキーに格納する

  4. リクエストの中からauthという名のcookieを探し、エラーがない場合は、cookieの値をデコードする  データは、UserDataというキーで、dataマップに格納される

  5. t.templ.Execute(w, data)でHTMLを作成し、 HTTPレスポンスとして書き込む

JSON形式での送信
WriteJSONメソッドを使用することで、オブジェクトをJSON形式にして、WebSocket経由で 送信を行う。

第1章での実装

func (c *client) write() {
    for msg := range c.send {
        if err := c.socket.WriteMessage(websocket.TextMessage, msg); err != nil {
            break
        }
    }
    c.socket.Close()
}

[]byte型のmsgをそのまま、websocket.TextMessageタイプで送信

第2章での実装

func (c *client) write() {
    for msg := range c.send {
        if err := c.socket.WriteJSON(msg); err != nil {
            break
        }
    }
    c.socket.Close()
}

msgを、JSON形式でエンコードし送信を行う

メッセージの受信者(クライアント型)は、メッセージを自動的に変換されたJSON形式で受け取る websocket.TextMessageのように、メッセージタイプを指定する必要がなくなる

msgとは??
message構造体のインスタンス
chatでの、メッセージ内容などの情報をもつインスタンス

type message struct {
    Name    string
    Message string
    When    time.Time
}

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
本日はここまでで、また後日続きを行います

『Go言語によるWebアプリケーション開発』学習メモ(1日目)

『Go言語によるWebアプリケーション開発』という本での学習の中で、 学んだことを自分用に整理してメモしていきます。

www.oreilly.co.jp

第1章 WebSocketを使ったチャットアプリケーション

package main

import (
    "github.com/gorilla/websocket"
)

type client struct {
    socket *websocket.Conn
    send   chan []byte
    room   *room
}

Channelは、ゴルーチン間で、データの送受信を行うための機構を構築する
Channelはバッファを持つことが出来る

バッファのあるChannelでは、指定された容量分、データを保持できる
送信ゴルーチンでは、容量がいっぱいになるまで、データを送信
受信ゴルーチンでは、バッファが空になるまでデータを受信できる

複数の送受信者が、同時に読み書きすることが出来る

バッファとは
一時的にデータを保存する領域のこと
サーバーなどで複数のクライアントからリクエストが来た際に、
同時に処理することはできないため、一時的に保存したりする
c.room.forward <- msg

チャネル演算子で値を送受信することができる

並列処理と並行処理
並列処理:複数のタスクを同時に処理する
並行処理:複数のタスクを短時間で切り替える

チャットルームの作成
type room struct {
    forward chan []byte
    join    chan *client
    leave   chan *client
    clients map[*client]bool
}

なぜmapを使うのか?

const (
    socketBufferSize  = 1024
    messageBufferSize = 256
)

var upgrader = &websocket.Upgrader{ReadBufferSize: socketBufferSize, WriteBufferSize: socketBufferSize}

func (r *room) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    socket, err := upgrader.Upgrade(w, req, nil)
    if err != nil {
        log.Fatal("serveHTTP:", err)
        return
    }
    client := &client{
        socket: socket,
        send:   make(chan []byte, messageBufferSize),
        room:   r,
    }
    r.join <- client
    defer func() { r.leave <- client }()
    go client.write()
    client.read()
}
  1. ServeHTTP関数で、HTTPリクエストを受け取り、WebSocketの接続にアップグレード
  2. 成功後、Clientを作成し、チャットルームに追加
  3. ClientはWebSocketの接続、送信用のチャンネル、チャットルームを保持する
  4. Clientの終了時に退出
  5. goroutinueで、読み書きを行い、Clientはメッセージの送受信を同時に行う
websocket

クライアントとサーバー間でのリアルタイム通信を実現する通信プロトコル

メッセージの送信が行われている

ターミナルにログを出す
tracer.go

type Tracer interface {
    Trace(...interface{})
}

type tracer struct {
    out io.Writer
}

func (t *tracer) Trace(a ...interface{}) {
    t.out.Write([]byte(fmt.Sprint(a...)))
    t.out.Write([]byte("\n"))
}

func New(w io.Writer) Tracer {
    return &tracer{out: w}
}

Tracerインターフェースを定義し、Traceメソッドを定義
New関数で、新しいTracerを作る

trace.go

func (t *tracer) Trace(a ...interface{}) {
    t.out.Write([]byte(fmt.Sprint(a...)))
    t.out.Write([]byte("\n"))
}

a ...interface{} 任意の長さの引数を受け取ることが出来る
Writeメソッドは2回呼び出され、1回目は、文字列の出力
2回目は、改行を行っている

room.go

type room struct {
    forward chan []byte
    join    chan *client
    leave   chan *client
    clients map[*client]bool
    tracer  trace.Tracer
}

roomは、Tracerインターフェースを持つ
構造体を定義し、フィールドを作る
フィールド経由で、Traceメソッドを呼び出すことが出来る

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
本日はここまでで、また後日続きを行います

JavaScriptで順列を生成するライブラリをつくる

はじめに

JavaScriptで配列の要素をすべて並び替えて、順列を生成するライブラリを作成したので、 その過程を記事にしてみました。

既に多くの方が、npm packageとして公開されているのですが、 コードを書いたので、ライブラリとして公開してみました。

www.npmjs.com

コード

function generatePermutations(arr, currentPermutation = []){
    if (arr.length === 0) {
        return [currentPermutation];
    }

    let permutations = [];
    for (let i = 0; i < arr.length; i++) {
      const remainingElements = arr.slice(0, i).concat(arr.slice(i + 1));
      const newPermutation = currentPermutation.concat(arr[i]);
      permutations = permutations.concat(generatePermutations(remainingElements, newPermutation));
    }
    return permutations;
}

module.exports = {
  generatePermutations
};

解説

function generatePermutations(arr, currentPermutation = []){
  

generatePermutationsという関数を定義し、引数には以下を指定します
arr : 元の配列
currentPermutation : 順列を追跡していくための空の配列

if (arr.length === 0) {
        return [currentPermutation];
    }

ここで、もし、arrの長さが0だった場合、currentPermutationを返します
再帰関数の終了条件です

    let permutations = [];

生成された順列を保存する空配列

 for (let i = 0; i < arr.length; i++) {
      const remainingElements = arr.slice(0, i).concat(arr.slice(i + 1));
      const newPermutation = currentPermutation.concat(arr[i]);
      permutations = permutations.concat(generatePermutations(remainingElements, newPermutation));
    }

arrの長さ文、ループを回します。
remainingElements : arrから、現在の要素を取り除いた新しい配列を生成している
          arr0番目の要素から、i - 1番目の要素までを取得
          arr.slice(i + 1)で、i + 1番目の要素から、最後の要素を取得
          concatメソッドで、その2つの部分配列を結合
          現在のi番目の要素を除いた新しい配列が完成する

newPermutation : 現在の順列(currentPermutation)にarr[i]の要素を追加した新たな配列
permutations : 再帰的に順列生成の関数を呼び出し、その結果を順列リストに追加

それぞれの再帰のステップで、配列の1つの要素を指定し、その要素を固定し
その残りの要素ですべての順列を生成している
これをすべての要素で処理を実行している

テストコード

テストフレームワークのJestを使用し、テストのコードを書いてみました

const { generatePermutations } = require('../js/index.js');

describe('generatePermutations', () => {
    test('good permutations', () => {
        const input = [1, 2, 3];
        const expected = [
            [1, 2, 3],
            [1, 3, 2],
            [2, 1, 3],
            [2, 3, 1],
            [3, 1, 2],
            [3, 2, 1],
        ];

        const result = generatePermutations(input);
        expect(result).toEqual(expect.arrayContaining(expected));
        expect(result.length).toBe(expected.length);
    });

    test('empty array', () => {
        const input = [];
        const expected = [[]];

        const result = generatePermutations(input);
        expect(result).toEqual(expected);
    })

npm packageとして公開

最後に、npm packageとして公開し、どなたでもnpm installできる状態にします

npmでアカウントを作成し、npmにパッケージを公開します

www.npmjs.com

公開の手順は、公式のドキュメントを参考にしました docs.npmjs.com

npm init -y

公開したいファイルのディレクトリに移動し、上記のコマンドを実行します

コマンドを実行すると、package.jsonが作成されます

npm login

その後、コマンドを実行しnpmにログインをします

npm publish

上記のコマンドを実行し、npm packagepackageをnpmレジストリに公開することが出来ます

おわりに

思っているよりも、簡単にnpm packegeを公開できることが分かりました
今後も、他にもライブラリを作成できるように頑張っていきたいです(^^)