第2の人生の構築ログ

自分の好きなことをやりつつ、インカムもしっかりと。実現していく過程での記録など。読書、IT系、旅行、お金に関係する話などの話題。

mac でターミナルが開かなくなる - zsh 使用中

突然、mac でターミナルが開かなくなる状況に遭遇しました。厳密には、開いた後、諸々の情報を読み込んだ後にプロセスが終了してしまい、何もできないという状況です。

最後の git コマンドがないなんていうエラーは、そんなことはないはずなので、意味がわかりません。。何らかのアクセス権の問題なのでしょうが、1つ目にある Xcode ライセンスに同意しろというメッセージがこの原因のようです。確かに、Xcode のアップデートをした後にはなります。(その後何度か再起動はしていたような気もするのですが、なぜこのタイミングで?・・・)

以下のコマンドを発行して同意する意欲は満々なのですが、ターミナルが起動しないため、手が出せません。ターミナル無しでどうしろと・・・

sudo xcodebuild -license

と、以下のサイトに答えがありました。ありがとうございます。

hayashier.com

[ターミナル]->[環境設定] から環境設定画面を開き、そこで、開くシェルを切り替えが行えるようです。

"デフォルトのログインシェル"(zsh)から bash に切り替えます。

サイドターミナルを立ち上げると bash で起動できました。よかった。。

そして、Xcode のライセンスに同意します。

再度設定を"デフォルトのログインシェル"(zsh)に切り替えて問題なく起動することを確認しました。

ターミナルが起動しないなんて経験は初めてで、ターミナル起動しない状況で何の対応ができるんだ???と一瞬焦りましたが、、よかったです。

Jamstack 事始め - Astro でサイトを作成し、Cloudflare でホスティング (その2: Astro でサイトを作成する)

Image Credits: CSS-tricks.com

Jamstack 事始めシリーズ第1回では、Jamstack とは何かで終わってしまいましたので、今回は Astro とは何かを記載した後に、実際に Astro を使ったコンテンツの作成とその動作確認までを行ってみます。

Astro とは?

astro.build

Build faster websites.
Pull content from anywhere and serve it fast with Astro's next-gen island architecture.

Static Site Generator ツールの1つですが、他にも多くあるツールと何が違うのでしょうか。
Why Astro? 🚀 Astro Documentation で5つほど上げています。

  1. コンテンツ重視 (Content-focused)
    • Astro はコンテンツが豊富な Web サイトを構築するために設計されています。
  2. サーバーファースト (Server-first)
    • HTML をサーバでレンダリングすることで、Web サイトの動作が速くなります。
    • Astro は、クライアントサイドレンダリングよりもサーバサイドレンダリングを可能な限り活用しています。
    • Astro は、SPA (Single Page App) ではなく、MPA (Multi Page App) アプローチを採用しています。
  3. デフォルトで高速 (Fast by default)
    • Astro で遅い Web サイトを構築することは不可能です。
    • 良いパフォーマンスはいつも需要であり、コンテンツにフォーカスした Web サイトでは特に重要です。
  4. 簡単に使える (Easy to use)
    • 専門家でなくても、Astro で何かを構築できます。
    • Astro の目標は、全ての Web 開発者がアクセスできること。
      Web 開発のスキルレベルや過去の経験にかかわらず、親しみやすいと感じられるように設計されています。
  5. 充実した機能と柔軟性 (Fully-featured, but flexible)
    • 100以上の Astro インテグレーションから選択できます。
    • Astro は、Web サイトを構築するために必要なものが全て揃ったオールインワンの Web フレームワークです。
    • Astro は UI に依存しないので、Bring Your Own UI Framework (BYOF) が可能です。
      (React、Preact、Solid、Svelte、Vue,、Lit は全て正式に Astro でサポートされています。 )

新鋭のツールで、まだ日本ではそこまで名前を聞きませんが、少しづつ、個人、企業でも試されてきているツールのようです。

ローカル環境で Astro を使ってサイトを作成

npm を使ってサイトを作成します。

$ npm create astro@latest
Need to install the following packages:
  create-astro@1.2.3
Ok to proceed? (y) y

╭─────╮  Houston:
│ ◠ ◡ ◠  Initiating launch sequence...
╰─────╯

 astro   v1.6.10 Launch sequence initiated.
 
? Where would you like to create your new project? › my-astro-site
✔ Where would you like to create your new project? … my-astro-site
✔ How would you like to setup your new project? › a few best practices (recommended)
          ■■▶ Copying project files...(node:88874) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
✔ Template copied!
✔ Would you like to install npm dependencies? (recommended) … yes
✔ Packages installed!
✔ Would you like to initialize a new git repository? (optional) … yes
✔ Git repository created!
✔ How would you like to setup TypeScript? › Strict
✔ TypeScript settings applied!

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./my-astro-site
         Run npm run dev to start the dev server. CTRL+C to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at https://astro.build/chat

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut!
╰─────╯

プロジェクトにmy-astro-site という名前を付けていましたので、コマンドを起動したディレクトリにmy-astro-siteディレクトリが作成されております。移動します。

$ cd my-astro-site

package.json の中を確認してみます。

{
  "name": "@example/basics",
  "type": "module",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "astro": "astro"
  },
  "dependencies": {
    "astro": "^1.6.10"
  }
}

ローカルでサーバを起動してコンテンツを確認します。

$ npm run dev

> @example/basics@0.0.1 dev
> astro dev

  🚀  astro  v1.6.10 started in 40ms

  ┃ Local    http://localhost:3000/
  ┃ Network  use --host to expose

ちゃんとローカルサーバが起動してコンテンツの確認がとれました。

一旦ここで git の commit を行い、Github にプッシュしておきます。 最終的には、Github と連携してホスティングを行う Clouldflare にデプロイするようにします。

次回は生成したコンテンツを Clouldflare でホスティングします。

【Go言語】IntelliJ IDEA を使っている時に(f *os.File) Close()、os.Interrupt などが認識されない

Go のバージョンを 1.9 に上げた以降、(f *os.File) Close()os.Interrupt が急に IntelliJ IDEA で認識されなくなってしまいました・・・

[

何も悪いことはしておらず、何も問題ないはずなのですが・・・はて?何を怒られているのでしょうか・・・

結構同じような方がいらっしゃいますね。

Please update GoLand to the latest version or add unix build tag in Preferences | Go | Build Tags & Vendoring | Custom tags.

(f *os.File) Close() function not recognized – IDEs Support (IntelliJ Platform) | JetBrains

GoLand を最新版にアップデートするか、設定に unix build tag を追加せよ、とあります。

私の場合は、IntelliJ IDEA Ultimateで Go プラグインを使っているのですが、

[Preferences] -> [言語 & フレームワーク] -> [Go] -> [ビルドタグとベンダリング] にカスタムタグを見つけられました。

確かにunixと入力することで解決しました。

普通であれば、ちゃんと理由、原因まで抑えたいところですが、ツール側の問題で、他の方もこの対応で問題ないようですので、これで良しとします。

IntelliJ は結構安定しており、あまりこの手の事で悩むこと少ないのですが、ツールを挟むことに依るこの手のエラーは、本質的ではないところに時間を食ってしまうので、ちょっと嫌ですね。。

【Go言語】チャネル (channel) のクローズ (close) 〜 チャネルのバッファ内の値を確実に全て取り出す

チャネルの状態には、オープンとクローズ(closed)の状態があり、make することで生成された チャネル はオープンな状態となり、close(チャネル) で明示的に閉じることができます。
チャネル が close されたことを複数のゴルーチン (goroutine) へ一斉に伝えることができますので、close 関数を使ってブロードキャストによるキャンセル処理を実装できます。

なお、この手の処理は今はコンテキスト処理を使って行う方がよいのだと思います。ただ、この素の状態でのやり方も見ておいて損はないでしょう。。

以下は状況把握のためのサンプルになります。

3つのバッファをもつ int 型の チャネル を作成し、値を3つ送信した後に直ぐにクローズします。

case i, more := <-ch: については補足します。
チャネル を受信する際に戻り値に2つの変数を用意すると、1つ目(i)にはその値、2つ目(more)にはチャネルのオープン状態の真偽値(true/false)が入ってきます。 以下はチャネルを受信する際のイディオム for ループ、select の構文となっていますが、select でチャネルを受信後、そのチャネルのオープン状態(more)をチェックすることで処理の終了(ループの終了)を判断しています。

package main

import "fmt"

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    for {
        select {
        case i, more := <-ch:
            if more {
                fmt.Printf("OpenStatus: %v, value: %d\n", more, i)
            } else {
                fmt.Printf("OpenStatus: %v, value: %d\n", more, i)
                fmt.Println("exit for loop")
                goto L
            }
        }
    }
L:
    fmt.Println("Finish!")
}

上記の実行結果は以下の通りです。

OpenStatus: true, value: 1
OpenStatus: true, value: 2
OpenStatus: true, value: 3
OpenStatus: false, value: 0
exit for loop
Finish!

この結果からわかることとしまして、

  • チャネルはクローズされていても、また、バッファ内が空になっていても受信は行える。
  • チャネルのバッファが空で、かつ、クローズされた状態の場合に、チャネルのオープン状態を保持する変数(more)はfalseになる。

この結果からもチャネルのオープン状態を保持する変数(more)は確実にチャネルのバッファ内にある値をもれなく取り出せることを保証していますので、上記のような最後まで取り出して完了、という判断に利用できます。

Jamstack 事始め - Astro でサイトを作成し、Cloudflare でホスティング (その1: Jamstackとは何か?)

Jamastack とは何か

Jamstackという言葉はそれが世に出てきた時と現在では意味するところが異なっています。元々は使用されている技術 stack の頭文字をとって付けられたもの(JavaScript、API、Markup)ですが、現在は「分離」、"decoupling" を示すアーキテクチャとしてのアプローチの意味があります。

現在の公式サイトでは以下のように定義しています。

What is Jamstack?

Jamstack is an architectural approach that decouples the web experience layer from data and business logic, improving flexibility, scalability, performance, and maintainability.

Jamstack removes the need for business logic to dictate the web experience.

It enables a composable architecture for the web where custom logic and 3rd party services are consumed through APIs.

For fast and secure sites | Jamstack

目的としては、"improving flexibility, scalability, performance, and maintainability." (柔軟性、拡張性、パフォーマンス、保守性を向上させる) ものと言っています。
ではどうやって?、"an architectural approach that decouples the web experience layer from data and business logic" (Webエクスペリエンス層をデータやビジネスロジックから切り離すアーキテクチャ・アプローチ)と。
後者の表現は少しわかりにくいのですが、私の中で1番しっくりきていた説明は、Software Design 2022年11月号の「いまJamstackを始める理由」で記載されていた以下の記載です。

Jamstack において、最も重要なのは「分離」と「事前レンダリング」です。HTML を作る「ビルドプロセス」と配信する「ホスティングプロセス」を分離し、さらに静的な HTML によるフロントエンドと API によるバックエンドを分離します。

Software Design 2022年11月号|技術評論社

Jamstack のコンセプトを取り込んだコンテンツの作成

Jamastack のコンセプトを取り込んだ上でのコンテンツの作成、構成は以下のようになると思っています。

  1. コンテンツは静的サイトジェネレータを使って事前に作成 (SSG(Static Site Generation))
    【ローカル(or Github Actions など)で実行】
  2. 作成されたコンテンツは HTTP サーバの機能をもつホスティングサービスで公開
    【ホスティングサービスを利用】
  3. 静的サイトを作成する際に可能な限り動的な情報も取り込んで HTML に落とし込む
    【ローカル(or Github Actions など)で実行】
  4. どうしても静的サイトを作成する際に HTML に落とし込めない情報は情報が必要なタイミングで動的に情報を取得する (SSR(Server Side Rendering))
    【ホスティングサービスを利用】

先程の Software Design の記事の引用で、

HTML を作る「ビルドプロセス」と配信する「ホスティングプロセス」を分離し

とありましたが、「分離」というところを意識するために【】で"ローカル(or Github Actions など)で実行"、"ホスティングサービスを利用"と入れてみました。

「ビルドプロセス」は、1と3、「ホスティングプロセス」の部分は、2と4にあたります。4 で「どうしても」とありますが、1〜3だけで構築、運用可能な Web サイトは結構あると思います。この場合、所謂静的サイトとなりますので、ホスティングサービスに求められるものは、純粋な Web サーバの機能だけです。自前でサーバを持たなくても、AWS の S3、GCP の Cloud Storage にファイルを置くだけでもう配信が可能です。各社ちょっとした CDN のサービスも合わせて提供してくれるので、ホントに簡単なサイトであればもう充分です。
コンテンツの作成だけでにフォーカスし、ホスティングの部分は任せてしまいます。

餅は餅屋ということで、ホスティングの部分を適切なサービスに任せる(分離する)ことで、Jamstack を採用する上での"flexibility, scalability, performance (柔軟性、拡張性、パフォーマンス)"の向上が低コストで満たせます。
また、Jamstack は Secure だとも言われます。そう、ホスティングサービスにちゃんとしたサービスを使用しておけば、Webサーバを稼働させているインフラのセキュリティ、Webサーバの脆弱性などへのセキュリティ対策などは、サービサーに任せておけばよく、ちゃんとした品質で提供されておりますので安心して運用できます。

では、静的サイトでない場合、4 のケースはどうなるのでしょう。
この部分は所謂 servrless と言われる環境を使うのが Jamstack のコンセプトの中では言われています。こちらも自前でサーバ環境を用意するのではなく、既にプラットフォームが用意してくれているランタイム環境を使ってアプリを動作させるというものです。今ですと、JavaScript の動作環境、Node.js が主流なのかと思います。

以降でホスティングサービス、コンテンツを生成するジェネレータを整理します。

Jamstack ホスティングサービス

上記1〜4をカバーするホスティングサービスは幾つかありますが、私がよく聞くところでは以下のようなサービスです。

ホスティングサービスに関しては、今回は、最後の Cloudflare Pages を使って実際に試してみます。

Jamastack コンテンツジェネレータ

コンテンツを生成するジェネレータにもいろいろあります。

コンテンツジェネレータと言った時、「事前に静的コンテンツを生成するアプローチ」を行うツールだけが一昔前の一般的な認識ではなかったかなと思います。現在はそれに加え、「段階的に静的コンテンツを生成するアプローチ」、「サーバサイドでリクエストに応じて動的に生成するアプローチ」を機能として組み込んだものがあります。

実際のツールを取り上げる前に静的コンテンツの生成に対するアプローチを整理します。 コンテンツを生成するアプローチとしては、大きく以下3つパターンに分類されるのではないでしょうか。

  1. 事前に静的コンテンツを生成するアプローチ
    • SSG (Static Site Generation)
  2. 段階的に静的コンテンツを生成するアプローチ
    • ジェネレータを動作させたタイミングで事前に静的コンテンツを生成するという意味では、1.の SSG の仲間ですね。SSGの拡張版です。
    • 更に、ISG (Incremental Static Generation) と ISR (Incremental Static Regeneration) の2パターンがあります。
  3. サーバサイドでリクエストに応じて動的に生成するアプローチ
    • SSR (Server Side Rendering)
  4. クライアントサイドで動的に生成するアプローチ
    • CSR (Client Side Rendering)

SSG、ISG、ISR、SSR、CSR と、いろいろありますね。。

事前にコンテンツを生成する、という意味では、1、2が仲間です。 1と2は事前に静的コンテンツを生成するという意味では同じことなのですが、2の場合にはコンテンツ生成時に必要に応じて別のリソースにアクセスを行い、その時点の最新の情報を取り込んで静的コンテンツを生成する仕組みになります。

動的にコンテンツを生成するという意味では、3、4が仲間です。(実行環境はサーバとクライアントで異なりますが)

ツールによって、上記の1〜4を全て実現できるもの、その実現方法、異なりますが、jamastackのサイトでは以下のようなツールが紹介されています。

コンテンツジェネレータに関しては、今回は Astro を使ってみます。
全てのツールを試した訳でないのですが、紹介されているツールの中でも後発のものなり、React、Vue といった UI フレームワークを自由に選択できるというところから試してみようと思っています。

Jamstack の採用は進んでいる??

個人的には Jamstack のコンセプトはとても合理的でその方向に進むのがよいのだろうなぁと思っているのですが、徐々に採用を試し始めている話を聞き始めているのですが、イマイチ日本ではまだ浸透していないように思えます。

個人的にはその理由としては、CI/CD がなかなか浸透できない開発環境における開発体制、組織体の問題なのではと思うところがあります。 Jamastack はアプローチの話とありましたが、環境のアーキテクチャの話だけではなく、公式サイトでも"improving flexibility, scalability, performance, and maintainability"と記載がありましたように、コンテンツの作成、リリース、メンテナンスといった運用まで含めた話となり、そのメリットは運用も含めての話になっていると思います。 そうなりますと、旧態依然とした環境、体制のもとではその効果が発揮できず、なかなか導入も進まないのではないかなと。

小回りの効く体制をとれる組織体で成功事例を作っていき、広く広まっていくとよいなぁと思っている今日この頃です。

この記事はここまでです。次回から実際のコンテンツの生成、ホスティングサービスへのデプロイなどを試していきます。

【Go言語】html/template で、意外と分かり難い"Uncaught SyntaxError: Unexpected token '<'" エラーの原因

久しぶりに Go 言語のテンプレート(html/template)を使用し、テンプレートの HTML から外部ファイルを読み込もうとしていました。そこで遭遇した以下のエラー。

原因がわかってしまえば、そりゃそうでしょう、となるのですが、以下のエラーだけ見ると、何が悪いのかさっぱりで、暫しキョトンとしてしまいました。

Uncaught SyntaxError: Unexpected token '<' f:id:dr_taka_n:20210903214623p:plain

よくわからないところに×点マークです。 f:id:dr_taka_n:20210903214620p:plain

文字通り読めば、何かタグの閉め忘れのような HTML の構文ミスをおかしているのではないかと考えます。しかし、何度見てもそこに問題はありません。

この時の一連のソースコードは以下の通りとなります。

$ tree
.
├── main.go
├── main.js
└── templates
    └── index.html

main.go

package main

import (
    "fmt"
    "html/template"
    "log"
    "net/http"
    "path/filepath"
    "sync"
)

const PORT = 8080

type templateHandler struct {
    once     sync.Once
    filename string
    tmpl     *template.Template
}

func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t.once.Do(func() {
        t.tmpl = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
    })
    if err := t.tmpl.Execute(w, nil); err != nil {
        log.Fatal("template ServeHTTP:", err)
    }
}

func main() {
    http.Handle("/", &templateHandler{filename: "index.html"})

    if err := http.ListenAndServe(fmt.Sprintf(":%d", PORT), nil); err != nil {
        log.Fatal("ListenAndServe:", err)
    }
    log.Printf("Listening port %d ...", PORT)
}

ちなみに、本題とは関係ないのですが、リクエスト毎に処理を行うメソッドにて、テンプレートのコンパイルは当然ながら都度行う必要はありません。ですので、このメソッドでコンパイルを行うとした場合には、テンプレートのコンパイルは、syncOnceを使って一度だけ実行することを担保しています。

template/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>テンプレートサンプル</title>
</head>
<body>
  <p>こんにちは!</p>
  <script type="text/javascript" src="main.js"></script>
</body>
</html>

main.js

document.addEventListener("DOMContentLoaded", () => {
  console.log("DOMの読み込み完了!");
});

おそらく、Go のテンプレートを扱い慣れた人であればすぐに理由に気付いてしまうのではないでしょうか。

そう、テンプレートファイル(index.html)で読み込んでいる外部ファイル(main.js)がちゃんと読み込めていない、つまりは、Go のサーバから配信できていないことが問題になります。いやぁ、これはエラーの文言と睨めっこしてもわかりません。。

JavaScript のファイルも Go のサーバから配信してもらうようにします。

当然、通常の Static ファイルを公開する方法で大丈夫です。

まずは、静的コンテンツをおく場所を用意しておきます。

$ tree
.
├── main.go
├── public
│   └── js
│       └── main.js
└── templates
    └── index.html

public ディレクトリとその以下にjsディレクトリを作成し、元々用意していたmain.jsを配置します。

静的ファイルを配信するためのハンドラを用意します。

main.go のハンドラを設定している箇所に以下を追加します。

func main() {
    http.Handle("/", &templateHandler{filename: "index.html"})
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("public"))))  // 追加

http.FileServer はファイルを配信することができる http.Handler interface を実装した関数です。起点となるディレクトリを引数に渡しています。 http.StripPrefix関数を一枚挟んでいますが、これは、例えば、/static/js/main.js というパスでリクエストした場合に、/static/を strip (削除) し、起点として指定したディレクトリ(ここでは public)配下のパスを参照するという意味になります。

Go の実装は以上で、HTML の参照パスを変更しておきます。

templates/index.html

 </head>
 <body>
   <p>こんにちは!</p>
-  <script type="text/javascript" src="main.js"></script>
+  <script type="text/javascript" src="/static/js/main.js"></script>
 </body>
 </html>

対応は以上です。これで再度リクエストしてみると、エラーも消え、しっかり JavaScript ファイルも読み込めていることがわかります。

f:id:dr_taka_n:20210903214618p:plain

【Go言語】Go 言語で WebSocket を使ってみる

Webにおいて双方向通信を行うためのプロトコルの一つである WebSocket 使う必要があったので、まずはどのようなものか、Go 言語でシンプルに実装してみます。

実装するライブラリにはいろいろありそうですが、ここでは、golang.org/x で提供される準標準パッケージを使った場合と、gorilla を使った場合で試してみます。

golang.org/x を使った WebSocket

まずは準標準ライブラリの golang.org/x を使います。サーバ側の実装と、クライアントからその実装を試すための実装、2つを用意します。

ファイルの構成は以下の通りです。

$ tree
.
├── go.mod
├── go.sum
├── main.go
└── public
    ├── index.html
    └── main.js

サーバ側の実装は main.go 一本で、WebSocket のクライアント側は、public ディレクトリ配下の index.htmlmain.js で実装します。

サーバ側の実装です。

main.go:

package main

import (
    "fmt"
    "log"
    "net/http"

    "golang.org/x/net/WebSocket"
)

func webSocketHandler(ws *websocket.Conn) {
    defer ws.Close()

    // 初回メッセージを送信
    err := websocket.Message.Send(ws, "Server: Hello, Client!")
    if err != nil {
        log.Println(err)
    }

    for {
        msg := ""
        err := websocket.Message.Receive(ws, &msg)
        if err != nil {
            log.Println(err)
            break
        }

        err = websocket.Message.Send(ws,
            fmt.Sprintf("Server: '%s' received.", msg))
        if err != nil {
            log.Println(err)
            break
        }
    }
}

func main() {
    files := http.FileServer(http.Dir("public"))
    http.Handle("/", files)
    http.Handle("/ws", websocket.Handler(webSocketHandler))


    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

2つのハンドラを用意していますが、1つ目は / のパスに割り当てた静的ファイルを配信する部分です。この静的ファイルで WebSocket のクライアント側を実装します。静的フィアルの配信には、http.Handler を実装した http.FileServer 関数を使っています。

2つ目の/wsパスに割り当てているハンドラが WebSocket のサーバ側の実装です。  

ハンドラに指定しているwebsocket.Handler 型の引数はwebsocket.Connのポインタを引数にもつ関数func(*Conn)となっており、実際の実装の部分は、webSocketHandler 関数の部分になります。 ちなみに、websocket.Handler 型は以下のように定義されており、ServeHTTP 実装しています。

type Handler func(*Conn)

func checkOrigin(config *Config, req *http.Request) (err error) {
    config.Origin, err = Origin(config, req)
    if err == nil && config.Origin == nil {
        return fmt.Errorf("null origin")
    }
    return err
}

// ServeHTTP implements the http.Handler interface for a WebSocket
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    s := Server{Handler: h, Handshake: checkOrigin}
    s.serveWebSocket(w, req)
}

クライアントへのメッセージの送信は、websocket.Message.Send で、クライアントからの受信は、websocket.Message.Receive で行われ、単純に最初にサーバからメッセージを送っておき、その後は無限ループの中でメッセージの受信、受信後にその内容を送る、といったことをやっています。echoサーバですね。

クライアントの実装です。Inputボックスに何かメッセージを入れて送信ボタンを押下すると上部に送信したメッセージを表示させるというものです。

public/index.html:

<!doctype html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>WebSocket example</title>
</head>
<body>
    <p id="output"></p>

    <input type="text" id="input">
    <input type="submit" id="btn" value="送信">

    <script type="text/javascript" src="main.js"></script>
</body>
</html>

public/main.js:

document.addEventListener('DOMContentLoaded', () => {
  let loc = window.location;
  let uri = 'ws:';
  if (loc.protocol === 'https:') {
    uri = 'wss:';
  }
  uri += '//' + loc.host;
  uri += loc.pathname + 'ws';

  const ws = new WebSocket(uri);
  ws.onopen = function()  {
    console.log('Connected');
  }

  ws.onmessage = function(evt) {
    let out = document.getElementById('output');
    out.innerHTML += evt.data + '<br />';
  }

  const btn = document.getElementById('btn');
  btn.addEventListener('click', () => {
    ws.send(document.getElementById('input').value);
  });
});

実行してみます。http://localhost:8080/ にアクセスしますと、まずはサーバからのご挨拶があります。

f:id:dr_taka_n:20210823214741p:plain

フォームのインプットフィールドにクライアントからのご挨拶を入れて、"送信"をクリックしてみます。

f:id:dr_taka_n:20210823215301p:plain

ちゃんとメッセージが戻(echo)されてきていますね。

f:id:dr_taka_n:20210823215347p:plain

gorilla を使った WebSocket

こちらは、サーバ側だけ記載しておきます。準標準ライブラリを使っていた方が長い目では良さそうな感じはしますが、gorilla の方がいろいろ進んではいるようです。

github.com

(2021/08/28)
f:id:dr_taka_n:20210823215532p:plain:w500

準標準ライブラリの実装と同様の実装を gorilla でも行ってみます。main.goのみの変更でそれ以外(クライアント)は同じものを使います。

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

// デフォルト値で初期化
var upgrader = websocket.Upgrader{}

func webSocketHandleFunc(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("upgrade:", err)
        return
    }
    defer ws.Close()

    err = ws.WriteMessage(websocket.TextMessage, []byte(`Server (gorilla): Hello, Client!`))
    if err != nil {
        log.Println("WriteMessage:", err)
        return
    }

    for {
        mt, message, err := ws.ReadMessage()
        if err != nil {
            log.Println("ReadMessage:", err)
            break
        }
        err = ws.WriteMessage(mt, []byte(fmt.Sprintf("Server (gorilla): '%s' received.", message)))
        if err != nil {
            log.Println("WirteMessage:", err)
            break
        }
    }
}

func main() {
    files := http.FileServer(http.Dir("public"))
    http.Handle("/", files)
    http.HandleFunc("/ws", webSocketHandleFunc)

    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

gorilla を使うと、WebSocket が通常の HTTP プロトコルの拡張であるということを実感します。(websocket.Upgrader{}.Upgrade) 多少、メソッド名の違いなどありますが websocket.Upgrader の初期化処理があるくらいで、データの受信/送信などのお作法も大きく変わらず、このレベルの実装では大きな違いは無さそうです。

パスへの割り当ては、ハンドラではななくハンドラ関数での方で行っています。

クライアントから突っついてみます。結果は先程と同様になります。

f:id:dr_taka_n:20210827161452p:plain

どちらを使っても手軽に実装できるようになっていますね。

新たに外付けキーボードを導入。母艦とは異なる配列のキーボードを使用する〜母艦US(英語)->外付けJIS(日本語) Windows10編

個人でメインで使っているのは macOS の JIS(日本語)キーボードなのですが、会社の PC は Windows で US(英語)キーボードになっています。以前、US(英語)キーボードの Win ノートパソコンでJIS(日本語)キーボードの Apple Wireless Keyboard を使うという記事を書いていました。

www.morelife.work

実はこの後、ブルートゥースの接続が微妙、そもそも Windows に Mac のキーボードってちょっと使い難い・・・ということで母艦のキーボードをそのまま使い続けていました。

新たに外付けキーボードを購入(ProgresTouch RETRO TKL)

と、ある日、母艦のキーボードのEnterキーが割れてしまったのと、モニターを新調したことでWinノートへの外付けキーボードが改めて必要となったのでした。

今回はきちんと新規にキーボードを選ぶことにしました。(リモートワークになってからハードの購入が増えていますね。。)

  • 接続形態は無線(ブルートゥース)は避けて有線(USB)とする
  • 母艦はUSキーボードですが、外付けはJISキーボードとする
  • キーボードのタッチは触ってみて良さげなものとする

ということで、ビックカメラでいろいろ触って、最終的にはアーキサイト社の ProgresTouch RETRO TKL というキーボードにしました。

正直そこまでキーボードに精通している訳ではありません。店員の方に何軸が良いですか?と言われても「何ですかそのジクというものは?」というレベルで、キーボードのタッチって種類がいろいろあるのですね。

f:id:dr_taka_n:20200728141546j:plain:w500

私は「軽いキータッチと、スイッチが入る時にクリック感があります。」と説明のある「茶軸」が好みでしたので、「茶軸」にしました。

f:id:dr_taka_n:20200728172726j:plain f:id:dr_taka_n:20200728172737j:plain

持ち歩くものではないので良いのですが、意外と重くて、安定感があります。カタログでは 960g となっていますので、約1Kgありますね。。

細かいところでうれしいのがキー入力の切替を後ろについているピンで変更できるところです。私の場合、Win 系のキーボードの Caps キーを Control キーとして使うのに慣れていますので、Win キーボードにおいてはいつもレジストリーを変更することでキーマッピングを変えていましたが、この切り替えをキーボードの裏にあるピンでできるようになっているのは楽でうれしいですね。

f:id:dr_taka_n:20200728201317j:plain f:id:dr_taka_n:20200728201608p:plain (画像は ProgresTouch RETRO TKL(日本語配列) | 株式会社アーキサイト のページから)

JIS(日本語)キーボードとして使えるようにする

ここまででキーボード自体は使えるようになっていますが、PCの母艦はUS(英語)キーボードとして捉えていますので、キーマッピングがあっていません。前回同様ここはレジストリーを変更することで対応します。

まずはレジストリーで変更する対象を特定するために、先程取り付けた外付けキーボードをデバイスマネージャーで確認します。

f:id:dr_taka_n:20200808091840p:plain

幾つかキーボードが見えていますが、今回取り付けたものの情報を確認します。

f:id:dr_taka_n:20200808092102p:plain

レジストリーを特定するための "Device instance path" を確認します。日本語では、"デイバス インスタンス パス"となっていると思います。

f:id:dr_taka_n:20200808092146p:plainf:id:dr_taka_n:20200808092202p:plain

コマンドで、または、タスク バーの検索ボックスで regedit と打ち込み、レジストリエディタを起動します。

編集するパス、レジストリは以下のものになります。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\{確認したデバイスインスタンスパス}\Device Parameters

先程の "Device instance path" (デイバス インスタンス パス) の場合は、以下にあたります。

f:id:dr_taka_n:20200808092602p:plain

では値を実際に追加します。追加する値は以下の通りです。

Value name 値の種類 Value data
KeyboardSubtypeOverride DWORD 2
KeyboardTypeOverride DWORD 7

f:id:dr_taka_n:20200808093417p:plain f:id:dr_taka_n:20200808093433p:plain

最終的には以下のような感じになっています。

f:id:dr_taka_n:20200808093514p:plain

設定は以上です。再起動して使い始められます。

2週間使ってみての感想

実際に2週間使ってみての感想ですが、やはり有線の方が安定してよいですね。キーボードも打ちやすく満足です。一点、左側のシフトキーが何故か久しぶりに使う場合に一発目の押し込む感覚が微妙に引っかかった感覚があるのが気になるのですが、原因はまだよく分かっていません。シフトキーを外して中を見てみたのですが、何をしてよいのかわからず、そのままそっと蓋をしました。

ノートパソコンで外付けキーボードを使う場合のメリットは画面とキーボードの位置を自由に決められるところかなと感じています。 備え付けのキーボードの場合は、どうしてもノートの画面に合わせてキーボードを使わないといけないので、特に外付けモニターなどと一緒に使っている場合には不自然な状態でキーボードを使うことになります。無理のない場所にキーボードだけ移動して使うことのできる外付けであれば、体にも良いようで、肩こりがおさえられているように感じています。

【GCP】GAE プロジェクトで複数のサービスを構成する〜1プロジェクトで複数サブドメインを利用する

f:id:dr_taka_n:20200719171524p:plain

GCP の GAE (Google App Engine) では、1つのプロジェクトの中に複数サービスを立ち上げることができます。

ここで書いている「サービス」とは、app.yml で定義し、デプロイした1つ1つのアプリの単位をいっています。 以前以下の記事で書いたアプリの単位ですね。

www.morelife.work

何をやりたいのか

dispatch.yaml で定義を行うことでルーティングのオーバーライドができますので、指定された URL に対するサービスをマッピング(特定)することができます。それにより、以下のことができるようになります。

  • サービス毎に異なる言語を使いたい場合、言語/機能毎にサービスをわけることができます。
    • こっちのサービスは python で、こっちは Go で、みたいな。
  • カスタムドメインで1つのプロジェクトに追加できるのは1ドメインだけですが、複数のサービスを利用したいケースもあります。その場合、ホスト単位で分け(サブドメイン)たりしますが、このケースにも利用できます。

ここでは、タイトルの補足にありますように、後者にあたる「1プロジェクトで複数サブドメインを利用する」を行ってみます。以下のような構成を実現します。

  • www.example.com は、default サービス
  • a.example.com は、a サービス
  • b.example.com は、b サービス

前提と作業概要

まず、前提としてまして、

  • GAE をカスタムドメインで利用しています
  • すでにカスタムドメインの設定は行っており、サービス(app.ymlのアプリ)も1つ deploy しています

となっています。ちょうど以下の記事の作業を終えている状態となります。

www.morelife.work

ここでは、これに対して、以下の作業を行うことで「1プロジェクトで複数サブドメインを利用する」を実現します。

  1. 新たに追加するサービス用のサブドメインを追加する
  2. 新たなサービスを追加する
  3. dispatch.yaml でサブドメインとサービスのマッピングを行う

1. 新たに追加するサービス用のサブドメインを追加する

GAE の[設定] -> [カスタムドメイン] -> [カスタムドメインを追加] と辿ります。 f:id:dr_taka_n:20200718093555p:plain:w500

以下にありますようにここでの作業は3ステップです。1. 使用するドメインを選択する、2. 新たなドメインを現在のプロジェクトに指定する、3. DNS レコードを更新してセキュリティを有効化する。

f:id:dr_taka_n:20200718094238p:plain:w500

使用するドメインを選択して(「1. 使用するドメインを選択する」ここでは webapps-in.tokyo)、新たなサブドメインを追加します(「2. 新たなドメインを現在のプロジェクトに指定する」)。ここでは、sample-firestore.webapps-in.tokyo を追加しています。

f:id:dr_taka_n:20200718093338p:plain:w500

最後の「3. DNS レコードを更新してセキュリティを有効化する」ですが、ここでの作業ではありません。以下に表示されている DNS レコードを自身で管理している DNS に反映しておきます。以下には全ての情報がでていますが、今回は追加ですので、最後のCNAMEの部分だけが、新たに追加する必要のある情報です。

f:id:dr_taka_n:20200718094459p:plain:w500

DNS 側の設定に関しましてはここでは詳細は割愛します。以下の記事に記載していますので、そちらを確認ください。

www.morelife.work

サブドメインの追加は以上です。

2. 新たなサービスを追加する

新たなサービスをデプロイします。

先程サブドメインの追加でsample-firestore.webapps-in.tokyoを追加しました。このサブドメインに割り当てるサービスの登録を追加することになります。

簡単な GAE アプリを用意して、app.yaml を以下のように記述します。

runtime: go111
env_variables:
  PROJECT_ID: any_project_id
  VERSION: v1
service: sample-firestore

ポイントは、service の指定ですね。ここにサービス名を明示的に指定しています。指定しない場合は、default が指定されているものと見なされます。

GAE アプリの記載はここでは割愛します。以下の記事などを参照ください。

www.morelife.work

deploy します。

$ gcloud app deploy

3. dispatch.yaml でサブドメインとサービスのマッピングを行う

最後に追加したサブドメインと GAE アプリケーションのサービスを紐つけます。

現状サービスは2つあります。default サービスと sample-firestore サービスです。

適当な場所にdispatch.yamlを用意して、サブドメインとサービスのマッピングを以下のように記述します。

dispatch:
  - url: "webapps-in.tokyo/*"
    service: default
  - url: "www.webapps-in.tokyo/*"
    service: default
  - url: "sample-firestore.webapps-in.tokyo/*"
    service: sample-firestore

上記では、urlserviceの指定を1セットで3セット記載しています。

webapps-in.tokyo というドメインに対して、wwwwww 無し、sample-firestore の3つのホスト(サブドメイン)が用意されており、これは既にDNSで名前解決できる状態になっています。

それぞれのホスト(サブドメイン)に対して、GAE アプリケーションのサービスを指定しています。これは、app.yamlで指定を行ったサービス名になり、指定を行っていない場合には default が暗黙的に指定されたものと見なされています。

上記の指定により、www.webapps-in.tokyowebapps-in.tokyo にアクセスした場合には、default のサービス (GAE アプリケーション) が呼び出され、sample-firestore.webapps-in.tokyo にアクアセスした婆いには、sample-firestore のサービス (GAE アプリケーション) が呼び出されます。

dispatch.yaml をデプロイします。アプリのデプロイとコマンドが異なります。

$ gcloud app deploy dispatch.yaml

完了しますと、以下のようにサービスメニューにて「サービス」と「送信ルート」が設定されているのを確認することができます。

f:id:dr_taka_n:20200719165246p:plain

以上で、「1プロジェクトで複数サブドメインを利用する」を実現できました。

GAE の場合、サブドメインを追加する際に http だけでなく、https プロトコルでの口も用意してくれ、証明書も自動でそれぞれのホスト毎に作成してくれていますので、とても手軽に Web アプリの配置を行うことができるようになっています。

Let's Encrypt を使ってフリーでサーバ証明書を発行する

Webサイトの常時 HTTPS 化のこの時代、小規模なサイト、個人のサイトなど元々 HTTP で充分、というコンテンツを公開していた運営者の中には、サーバ証明書のコストを何とかできたら・・・と思っている方もいるかもしれません。

Let's Encrypt は、フリーで(機能的には他社の有料版と変わらない)サーバ証明書を発行してくれるサービスです。一昔前のサーバ証明書のお値段を考えると嘘のようですよね。
また一方では、最近ではクラウドプラットフォーム/サービス側でサーバ証明書をオプションで利用できるようにしていたりしますので、自身でサーバ証明書を用意して、、、というようなこともなくなってきているかもしれません。(Let's Encrypt が裏で利用されているもの多いようです。) 私の場合は、サーバ証明書がサービスとしてセットで利用できない環境にて Let's Encrypt を使ってサーバ証明書を発行して利用しています。

letsencrypt.org

今年の初めには発行した証明書が10億個を突破したということで話題になりました。

gigazine.net

また、そのよいニュースのすぐ後に Let's Encrypt のソフトウェアのバグにより、一部の発行した証明書を失効しないといけな事態が発生しておりました。

gigazine.net

該当のインシデントは以下の情報ですね。

影響を受けた関係者の方々、お疲れ様でした。

ちなみに、Let's Encrypt が発行するサーバ証明書は、ドメイン検証型の証明書です。サーバ証明書/HTTPS (SSL/TLS) を利用する目的としては、1) サイトの真正性、2) 通信の機密性(秘匿性)、3) データの完全性の担保というものがあります。ここで記載する Let's Encrypt で実現できるのは、2)と3)になり、1) までは担保してくれません。(サイトを運用している組織の証明まで行っているものではありません。)

Let's Encrypt を始めてみる

ではどうやって使い始められるのでしょうか。ホスティング・プロバイダが Let’s Encrypt をサポートしているケースは各サービスの利用ガイドにそって容易に始めえらるのではないかと思います。

ここでは、自分でサーバ証明書を作成して利用する方法の例を記載します。サーバ証明書の発行には、Certbot というコマンドラインツールを利用します。

certbot.eff.org

macOS の場合は、以下で Certbot のインストールを行えます。

$ brew install certbot

それ以外のプラットフォームに関しましては、Get Certbot — Certbot 1.3.0.dev0 documentation を確認ください。

certbot.eff.org

Certbot を使ってサーバ証明書を作成する

Certbotを使った発行にもいろいろとやり方がありますが、ここでは、マニュアルで発行を行い、発行された証明書(と秘密鍵)をサーバに配置するためのpkcs12というファイルフォーマットに変換してサーバに配置する、という流れで記載します。
ちなみに、マニュアルでの発行を行う場合、更新時も全く同じ作業を行うことになります。

証明書の発行は以下のようなコマンドで発行します。管理者権限が必要な処理がありますので、sudo が入っています。

$ sudo certbot certonly --manual -d www.example.com -d test.example.com

上記のコマンドで何が行われると言いますと、以下の2点です。

  1. 証明書に記載されるドメイン/ホスト(FQDN)の検証
  2. サーバ証明書の作成
    CSR の作成、証明書の発行

また、-d オプションの後にFQDNを与えて、2つ指定しています。これは何をやっているかと言いますと、「マルチドメイン(SANs)証明書」と言われるもので、複数の FQDN (上記例では、www.example.comtest.example.comの2つ)をこの1枚のサーバ証明書でまかないますよ、というものになります。最初に書いた FQDN (上記の例の場合はwww.example.com) が Common Name に使われます。先に実際に発行された証明書をみてみます。最初に指定した FQDN の名前(Common Name)で証明書が発行されています。

f:id:dr_taka_n:20200229234820p:plain

マルチドメインの場合には、その Common Name の FQDN、その他指定を行った FQDN は、SAN (Subject Alternative Name) に記載され、Common Name の FQDN だけでなく、SAN に記載された全ての FQDN でこのサーバ証明書が使えることになります。

f:id:dr_taka_n:20200229235201p:plain

実際のコマンドの実行結果をみていきます。

$ sudo certbot certonly --manual -d www.example.com -d test51.example.com 
Password:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): hoge@example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for test51.example.com
http-01 challenge for www.example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

ここまでは Yes or No のレスポンスを行うだけで、特に難しいことはありません。この後ちょっと作業が必要になります。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:

UehSy-qCCi-5UYcHCoRhe5q1OQpGMxYx-8XnCJ4TMcI.Ca4bn_Go9Bnvznx36-sQL22-kq117wbMGrKqm7llRzI

And make it available on your web server at this URL:

http://test51.example.com/.well-known/acme-challenge/UehSy-qCCi-5UYcHCoRhe5q1OQpGMxYx-8XnCJ4TMcI

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

上記は何かと言いますと、これが Certbot で実行される1番目の処理「1. 証明書に記載されるドメイン/ホスト(FQDN)の検証」になります。

上記では、2つ目に指定した FQDN test51.example.com のサーバに上記のパスで参照できる上記のデータ(UehSy-qCCi-5U...)を含むファイルを配置しておきない、後で確認するからね、ということを言っています。引き続きもう1つ指定したwww.example.com についても同様のことが指示されます。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:

W1UdvuPTDWcPygFKKXeN_kZ10Ozoiq4twuyLtWPp9N0.Ca4bn_Go9Bnvznx36-sQL22-kq117wbMGrKqm7llRzI

And make it available on your web server at this URL:

http://www.example.com/.well-known/acme-challenge/W1UdvuPTDWcPygFKKXeN_kZ10Ozoiq4twuyLtWPp9N0

(This must be set up in addition to the previous challenges; do not remove,
replace, or undo the previous challenge tasks yet.)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

コマンド上は上記まではまずは進められます。ここでエンターを押すと検証に入りますので、ここでエンターを押す前に実際に指定された情報の記載されたファイルをサーバに用意しておく必要があります。

準備後、エンターを押すと検証が走り、問題なければ(キチンと参照できれば)正常終了し、証明書が発行されます。

ちなみに、わざとではないのだが、以下はちょっとミスってしまった例です。問題無かったところからリトライができることの確認になりましたので、ミスった版で記載しておきます。

Waiting for verification...
Challenge failed for domain www.example.com
http-01 challenge for www.example.com
Cleaning up challenges
Some challenges have failed.

IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: www.example.com
   Type:   unauthorized
   Detail: Invalid response from
   http://www.example.com/.well-known/acme-challenge/W1UdvuPTDWcPygFKKXeN_kZ10Ozoiq4twuyLtWPp9N0
   [xxx.xxx.xxx.xxx]: "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD XHTML 1.0
   Strict//EN\"
   \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html
   xmlns=\"http:"

   To fix these errors, please make sure that your domain name was
   entered correctly and the DNS A/AAAA record(s) for that domain
   contain(s) the right IP address.
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.

上記では、test51.example.com は問題なかったのですが、www.example.com で失敗(ファイルの置き場所を誤っていた)しています。再度配置し直し(先にブラウザなどで直接アクセス確認をしておいた方がよいです)、コマンドをやり直します。

既に検証に成功している FQDN は処理をパスされ、失敗したホストだけ再検証されます。

$ sudo certbot certonly --manual -d www.example.com -d test51.example.com
Password:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for www.example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:

6s6Gr8sY4TV96z3yF9skkfyUVBhOdiH-us66fcAzO2U.Ca4bn_Go9Bnvznx36-sQL22-kq117wbMGrKqm7llRzI

And make it available on your web server at this URL:

http://www.example.com/.well-known/acme-challenge/6s6Gr8sY4TV96z3yF9skkfyUVBhOdiH-us66fcAzO2U

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/www.example.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/www.example.com/privkey.pem
   Your cert will expire on 2020-05-25. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

2020/05/25 に期限切れとなる90日間の証明書が無事発行されていることがわかります。

認証レベルという意味では、サイトの所有者ということだけでしか担保されていない証明書にはなりますが、これまで HTTP だけで公開していたサイトを常時 HTTPS 化に対応さえるという意味においては充分ですね。

発行されたサーバ証明書をサーバに配置する

やり方はいくつかあり、配置の仕方はサーバの仕様に依存します。ここでは、pkcs12 という形式でサーバに配置する方法を記載しておきます。

pkcs12 の作成には openssl を使います。pkcs12 は秘密鍵と証明書の双方を含むファイルです。秘密鍵を含むファイルですので、非常にセンシティブに扱う必要あります。pkcs12 ファイルの防御としてパスワードを指定します(コマンド実行後に入力させられます)。

引数に渡しているのは、サーバ証明書のファイル名と秘密鍵のファイル名です。それぞれコマンド終了後の最後のメッセージに表示されておりますので、それをそのまま指定します。
(コマンド自体に sudo は必要ありません。それぞれのファイルへのアクセスに必要となっています。)

$ sudo openssl pkcs12 -export -in /etc/letsencrypt/live/www.example.com/fullchain.pem -inkey /etc/letsencrypt/live/www.example.com/privkey.pem -out server-cert.pkcs12

上記で作成された server-cert.pkcs12 をサーバに配置します。

例えば、Tomcat 8 などの場合は、server.xml の中で以下のような指定を行い、作成された pkcs12 ファイルをサーバ証明書として利用します。

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true">
    <SSLHostConfig>
        <Certificate certificateKeystoreFile="/usr/local/tomcat/conf/server-cert.pkcs12"
                     certificateKeystoreType="PKCS12"
                     certificateKeystorePassword="password"
                     type="RSA"
                     clientAuth="false"
                     sslProtocols="TLSv1,TLSv1.1,TLSv1.2"
        />
    </SSLHostConfig>
</Connector>

以上です。

【Go言語】Bitbucket / Github の private リポジトリを Go Get する〜その2 ("410 Gone エラー")

Bitbucket / Github の private リポジトリを Go Get する方法を以前書いていたのですが、これだけでは現状失敗することに先日気付きました。

www.morelife.work

上記の設定を行っていたとしても、現状以下のような 410 Gone エラーに遭遇します。 以下は bitbucket の例ですが、github も同様でした。

$ go get -u -v bitbucket.org/drtaka/private_repository/package
go: finding bitbucket.org/drtaka/private_repository latest
go: finding bitbucket.org/drtaka/private_repository/package latest
go: downloading bitbucket.org/drtaka/private_repository v0.0.0-20200509073455-da8936b726a4
verifying bitbucket.org/drtaka/private_repository@v0.0.0-20200509073455-da8936b726a4: bitbucket.org/drtaka/private_repository@v0.0.0-20200509073455-da8936b726a4: reading https://sum.golang.org/lookup/bitbucket.org/drtaka/private_repository@v0.0.0-20200509073455-da8936b726a4: 410 Gone

環境は以下の通りです。

$ go version
go version go1.13.8 darwin/amd64

410 Gone の HTTP ステータスって分かり難いですよね・・・

developer.mozilla.org

当初、この事象への対処が全くわからなかったのですが、とりあえず暫定的な対応として以下が有効である確認がとれました。

github.com

$ export GO111MODULE=on
$ export GOPROXY=direct
$ export GOSUMDB=off

GO111MODULE の指定は go1.13 1.16 以降(※)であればいらないはずです。GOPROXYGOSUMDB に関しては、"Go モジュールのミラーリング・サービス【正式版】 — プログラミング言語 Go | text.Baldanders.info"に説明があります。

※ 1.16 からGO111MODULEの設定はデフォルトが on ですね。

text.baldanders.info

とりあえず前回の設定にプラスして上記(最低でもGOPROXY=directGOSUMDB=offは必須)の設定を入れておけば、410 Gone を回避でき、private リポジトリでも Go Get が可能です。

Windows10(US/英語キーボード)で Apple Wireless Keyboard (JIS/日本語キーボード)を使う

先日 Magic Trackpad の購入と合わせて、Apple Magic Keyboard の購入も行っておりました。PCスタンドを導入したタイミングに iMac を使っている妻から Apple Wireless Keyboard を譲り受け、以降 MBP で使っていたのですが、新たに購入した Apple Magic Keyboard の方へ切り替えを行っておりました。
好みはあるのでしょうが、Apple Wireless Keyboard のタッチはふにゃふにゃしていて若干深過ぎて、私には好みではありませんでした。Apple Magic Keyboard の浅めでシャキシャキしたタッチ感が好みです。 ちなみに上(右)が Apple Wireless Keyboard、下(左)が Apple Magic Keyboard になります。

f:id:dr_taka_n:20200506155720j:plain f:id:dr_taka_n:20200506155738j:plain

kiritsume.com

話がちょっと横道に逸れていますが、これまで使っていた Wireless Keyboard を Windows 10 で使おうというのが今回の試みです。ただ Bluetooth で繋げるだけでなく、1つ変わった点としては、会社のノート PC Windows は US キーボードになっています。ノート PC のキーボードは US キーボードのままで、今回使用する Wireless Keyboard は JIS (日本語)キーボードになりますので、JIS キーボード配列で使えるように設定します。

まずは Bluetooth で Windows10 と Apple Wireless Keyboard を接続します

Mac での接続設定はスンナリいったのですが、Windows ではちょっとハマりました。

ハマったところは、Bluetooth 接続を行うにあたり、PIN コードをキーボードから入力する必要があるのですが、確か最初の1回目はそのPINが表示されたのですが、その入力をミスってしまい、再度入力を試みようとしたところ、入力画面だけで PIN が表示されなくなってしまいました。何度やっても以下の画面しか出なくなってしいました。

f:id:dr_taka_n:20200506171252j:plain:w300

ここでもう手詰まりです。先に進めません。。さてこのような事態になった場合にどうするかです。

win.just4fun.biz

普通に [Blutooth デバイスの追加] で進めることはできませんので、[コントロールパネル] -> [デバイスとプリンター]からデバイスの追加を行います。

f:id:dr_taka_n:20200506171730j:plain

上のほうのメニューにある [デバイスの追加]をクリックしてデバイスを追加します。

f:id:dr_taka_n:20200506171743j:plain

"System Administrator's Keyboard" というのが今回追加しようとしている Apple Wireless Keyboard です。[次へ]で先に進ます。ちなみに、Apple Wireless Keyboard ではペアリングのために電源ボタンを長押しして緑のランプを点滅させています。

f:id:dr_taka_n:20200506172516j:plain:w500

パスコードの入力画面になっていますが、入力フィールドの下に「または、パスコードを接続先のデバイスで入力してください」というリンクがありますので、これをクリックします。

f:id:dr_taka_n:20200506172513j:plain:w500

やっと入力するためのPIN(パスコード)をみることができました!

f:id:dr_taka_n:20200506172510j:plain:w500

上記の番号を Wireless Keyboard に入力します。気をつけたいこととして、Mac の場合には入力している数値が画面側にもわかるような表示になるのですが、ここでは特にそのような表示はありませんので入力している値が果たして反映されているのかどうかわかりません(このため、私は初回の入力をミスったのでした)。何とも心もとないのですが、最後まで入力したら自分の入力を信じてエンターを叩きます。

入力に問題がなければ以下のように追加されます。

f:id:dr_taka_n:20200506173440p:plain

これで接続は OK です。

JIS (日本語) キーボードとして使えるようにします

Apple Wireless Keyboard を Windows 10 で使えるようになりましたが、PC側が US (英語) キーボードの設定となっておりますので、追加したキーボードも US (英語) キーボードとして認識されています。JIS (日本語) キーボードとして認めてもらいます。

先程接続した Wireless Keyboard デバイスのレジストリ情報を変更することで対応します。

まずは、先程接続したデバイスの特定からになりますが、これ私みたいにハードの知識がない人にとっては結構難しいですね。デバイスマネージャーを開きます。

f:id:dr_taka_n:20200506185517j:plain

"Keyboards" の下に幾つかデバイスがあります。どれが Wireless Keyboard なのでしょうか。。。

f:id:dr_taka_n:20200506185514j:plain

"Events"(イベント)タブにあるタイムスタンプで特定することにしました。初めて先程接続を行っていましたので、日時が新しいものになります。

f:id:dr_taka_n:20200506185818j:plain

特定しましたら、「デバイスインスタンスパス」の情報を控えておきます。以下の画面は英語になっていますが、日本語の場合は、「詳細」タブの「プロパティ」「デイバス インスタンス パス」の値になります。

f:id:dr_taka_n:20200506190238j:plain

Registry Editor を使ってレジストリの値を編集します。 コマンドで regedit を打ち込み、レジストリエディタを起動します。

編集するパス、レジストリは以下のものになります。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\{確認したデバイスインスタンスパス}\Device Parameters

上記を選択した状態で、[DWORD (32 ビット) 値]を追加します。[KeyboardSubtypeOverride]と[KeyboardTypeOverride]の2つです。 追加後、JISキーボードの場合には、それぞれの値を以下のように設定します。

  • KeyboardSubtypeOverride -> 2
  • KeyboardTypeOverride -> 7

設定後は以下のようになります。

f:id:dr_taka_n:20200506191139j:plain

www.softel.co.jp

設定はWindowsを再起動後に反映されていることを確認できます。

ノートPC側は US キーボード、外付け Apple Wireless Keyboard は JIS キーボードと、妙な状況ですが、、本体のキーボードの選択肢はありませんので、それ以外のキーボード設定を使いたい場合には、今回の設定で対応できます。 (数年 US キーボードで過ごしたこともありましたが、今は JIS をメインで使っています。会社ノートPCは問答無用でUSキーボード設定されるので選択肢がありません。)

Chrome の画面が特定のページで乱れるため暫定対応

一言で乱れると言いましても、よくわからないかと思います。yahoo のトップページを見ると以下のような感じです。

f:id:dr_taka_n:20200504125152p:plain

全てのサイトでこうなる訳ではなく、特定のサイトだけです。また、他のブラウザは全く問題なく、Chrome だけの問題です。
同じような事象に悩まれている方が居ないかと探したところ、全く一緒ではありませんが、以下のような事象、対応が確認できました。

に書かれていた Chrome の設定をいじってみようと思い、[環境設定]->[システム] の[ハードウェア アクセラレーションが使用可能な場合は使用する]の設定を確認したところ、既にOnになっていました。

f:id:dr_taka_n:20200504125623p:plain f:id:dr_taka_n:20200504125505p:plain

では、ということで、逆に Off にしてみました。

f:id:dr_taka_n:20200504125831p:plain

嘘のような話ですが、これで直りました。改めて On にしてみると画面が乱れてしまいますので、現時点の対応として Off で良いようなのですが、何が影響しているのでしょうか。

以前はこんなことはなくて、つい最近の話です。

環境は、

  • macOS Catalina 10.15.4 (MacBook Pro (16-inch, 2019)
  • Chrome バージョン: 81.0.4044.129(Official Build) (64 ビット)

になります。

しばし変更した設定で様子をみます。

マウスをやめて Apple Magic Trackpad 2 を導入〜とても快適です

きっかけは、リモートワークですね。もう四六時中キーボード打って、マウス使ってやっている訳ですが、腰に続き、肩が悲鳴をあげました。。(何か妙なきっかけで特に右肩の筋を痛めてしまったようです。)

マウスを動かすのすら辛くなってきました。こんなことは初めてです。。おそらく若くないことも原因なのでしょうが、妻にマッサージしてもらったり、湿布はったり、効き湯のお風呂にゆっくり入ったりといろいろやってみて、少しはよくなってきていたのですが、マウスを動かすのが微妙に神経に障り、ホントキツいのです。

私の作業環境はMacBook ProをノートPCスタンドで高い位置において、外部モニターをメインに使い、キーボードは外付けのものを使っています。 この状態でマウスを触るのがツライので、左手でMacBook Proのタッチパッドを使ってみたりしていたのですが、あれ?このタッチパッドが独立して外付けであれば、キーボードの横に置いて使うとすごく楽ではないか?と思うに至りました。

ガジェット系といいますか、ハードへの興味が薄いのであまり気にしていなかったのですが、Apple Magic Trackpad 2 というトラックパッドがまさにそれにあたるのですね。藁にも縋る思いで早速購入しました。

f:id:dr_taka_n:20200503185132j:plain:w500

Apple Magic Trackpad 2 - スペースグレイ

Apple Magic Trackpad 2 - スペースグレイ

  • 発売日: 2018/05/16
  • メディア: Personal Computers

キーボードの真横に置いて使っています。(充電中のためコードを繋いだままですが、ブルートゥース接続ですので、常時繋いでおく必要はありません。)

f:id:dr_taka_n:20200503185237j:plain:w500

もう、大正解でした。これは素晴らしいです。とても楽になりました。元々 MacBook Pro のトラックバックに慣れているので、使い方自体は苦にしないのですが、全く違和感なく操作でき、マウスと違い、指だけで操作できますので、肩に痛みが走らないのです!作業を行うのが気分的にとても楽になりました(ここ最近、ホント苦痛だったのです)。

人にも依ると思うのですが、肩への負荷はおいておいて、私の場合はトラックバックの方が操作し易く、作業効率も上がっています。Apple のハード作りの優秀さには改めて感動しました。もう今後はマウスを使うことはなくなりそうです。

Kyash Card 届きました〜ポイント還元率が下がる前に Kyash Card Lite から切り替えます

いろいろなキャッシュレス決済方法があり、私も幾つか使い分けていますが、そのうちの1つの Kyash のカードの切り替えを行うことにしました。 これまで使っていたものは、現在 Kyash Card Lite という名称になっており、新規に Kyash Card を申し込み、先日その実体のカードが届きました。Kyash Card のデザインには3種類あり、Navy にしました。(今申し込むと6月上旬の発送となるようです) スマートなデザインですね。

f:id:dr_taka_n:20200429102537j:plain:w300

現在 Kyash のカードには3つのタイプがあり、今回新たに発行した Kyash Card、これまで利用していた Kyash Card Lite、Kyash Card Virtual になります。

発行に900円かかり(最初だけで年会費などはありません)ますので、わざわざ切り替えなくてもと思っていたのですが、

  • Kyash Card Lite のポイント還元率が 1% から 0.5% に変更 (5/1から)。Kyash Card は 1% のまま。
    (昔は2%だったんですよね。。)
  • Kyash Card が IC チップ搭載、Visa タッチ決済にも対応。

の部分に反応して、切り替えることにしました。

3タイプのカードの違いは、以下のページの下の方、「KyashのVisaカードのラインナップ」に比較表があります。

kyash.co

小口と言いますか、ちょっとした買い物だけで使っていますので、そうそう機会はないのですが、以前ちょい大きな買い物の時に Lite は限度額が低くて使えない時があったので、決済限度額の違いもうれしいです。

どこで Kyash を使うのか

ちなみに、どこで Kyash を使っているかですが、私の場合は、

  • QUICPAY+ のメインカードとして
    • Google Pay の QUICPay のメインカードとして設定しています。コンビニ、その他お店にて還元率のいいキャッシュレス決済方法を利用していますが、QUICPay はクレジットカードが使えるお店ではだいたい使えますので、Suica などの交通系に次いで利用できる場所が多い決済方法かなと感じています。
    • クレジットカードから Kyash Card へのチャージでクレジットカード側に1%、QUICPay を使用した際のメインカードの Kyash Card で1%、最低でも2%のポイント還元がえられます。(キャッシュレスのポイント還元が行われるお店では当然その分のポイント還元も)
  • Amazon の支払など
    • ポイント還元のメリットは上記と同じです。普通に直接クレジットカードを使うより、一度 Kyash を経由させることで+1%になりますので、Kyash が使えるところでは Kyash を使った方がお得です。
    • 一度ギフト券を購入して利用することで細かいですが、効率的にポイントゲットできます。。
  • Suica のチャージ
    • 一昔前は Suica へのチャージでも Kyash 側にポイントがついていましたが、今はつきません。
    • Kyash 経由で Suica にチャージすることで、Kyash にチャージする際のクレジットカードには普通に1%つきますので、最低でも1%のポイント還元が得られます。

となります。

アプリを有効化

カードはカードでそのまま使えますが、Kyash アプリと QUICPay+ の利用カードの変更も行っておきます。

まずは Kyash アプリです。[アカウント]->[Kyash Card の有効化] で Kyash Card をアプリで有効にしておきます。

f:id:dr_taka_n:20200429104009p:plain:w300

有効化実施後の画面です。カード情報が Kyash Card に切り替わっています。

f:id:dr_taka_n:20200429110835p:plain:w300

うっかりしていたのが QUICPay+ の切り替えでした。カードの画像からもわかるように以前の Kyash Card Lite の設定のままになっています。[支払い]のメニューから「+ お支払い方法」で Kyash Card の情報を追加しておきます。

f:id:dr_taka_n:20200429111135p:plain:w300 f:id:dr_taka_n:20200429111455p:plain:w300

あまりやらない作業ですので、備忘のため、作業のスクリーンショットだけはっておきます。

f:id:dr_taka_n:20200429111650p:plain:w300 f:id:dr_taka_n:20200429111732p:plain:w300 f:id:dr_taka_n:20200429111811p:plain:w300 f:id:dr_taka_n:20200429111837p:plain:w300

QUICPay のメインカードとして設定されました。これで QUICPay でも Kyash Card が利用されることになりました。

f:id:dr_taka_n:20200429112057p:plain:w300 f:id:dr_taka_n:20200429112137p:plain:w300

ポイント還元率が変わる(5/1以降)ギリギリのところで、 Kyash Card Lite から Kyash Card に切り替えることができました。