Go言語で複数ドメインにも対応可能なHTTPSサーバーの作り方

少し前に『Go言語でシンプルで簡単なHTTPサーバーの作り方』という記事を書きました。今回は複数のドメインにも対応可能な『HTTPS』サーバーの作り方をご紹介します。

『HTTP』ではなく『HTTPS』と最後に『S』が付いているけど何か違うの?と疑問に思った方もいると思いますが、『HTTPS』はクライアントとの通信が暗号化されたプロトコルのことです。

例えば、オンラインショッピングでクレジットカードの番号や住所を入力することがありますよね。そんな時にサーバーとの通信が暗号化されていなかったら、悪い人達に番号や住所がバレちゃうかもしれません。でも『HTTPS』で暗号化されていればそのリスクは随分と減ります。

これからコードのご紹介と解説を行いまずが、いくつか難しい用語や簡単に済ませている部分も出てきます。その場合は『Go言語でシンプルで簡単なHTTPサーバーの作り方』に書かれていることもありますので、そちらもご覧頂ければと思います。

コンテンツの転載は固くお断りいたします。

コードのご紹介

少し長くなりましたが、以下が複数ドメインにも対応可能なHTTPSサーバーのコードの一例です。後ほど解説しますので、まずは簡単にでも目を通してみて下さい。


package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
	"time"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "サーバーからの暗号通信で安全になったお返事です!")
}

func main() {
	muxSSL := http.NewServeMux()
	muxSSL.HandleFunc("/", handler)

	tlsConfig := &tls.Config{}
	tlsConfig.Certificates = make([]tls.Certificate, 1)

	var err error
	tlsConfig.Certificates[0], err = tls.LoadX509KeyPair("FullChainFilePath", "PrivKeyFilePath")
	if err != nil {
		log.Fatal(err)
	}

	tlsConfig.BuildNameToCertificate()

	ServerSSL := &http.Server{
		Handler:        muxSSL,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	listener, err := tls.Listen("tcp", ":443", tlsConfig)
	if err != nil {
		log.Fatal(err)
	}

	err = ServerSSL.Serve(listener)
	if err != nil {
		log.Fatal(err)
	}
}

コードの解説

では、以下にコードの解説を書いていきます。

必要なパッケージを宣言する


import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
	"time"
)

HTTPサーバーの時の比べ、新たに増えたのは『crypto/tls』と『log』ですね。『crypto/tls』はHTTPSで必要となる暗号化(SSL)に関するパッケージで、『log』は名前そのまま『ログ』を表示するためのものです。

普通の『fmt.Print』では、ただたんに文字列などを表示しますが『log.Print』とすると、最初に現在の日付と時刻を表示し、その後に文字列を表示します。サーバーのログを表示する際、いつ起きたイベントか分かると便利ですよね。

サーバーからお返事をする


func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "サーバーからの暗号通信で安全になったお返事です!")
}

この部分はHTTPサーバーのものと変わりません。HTTPS化したからと言って、この部分に特殊な記述をする必要はありません。

アドレスによる処理の振り分けを登録する


muxSSL := http.NewServeMux()
muxSSL.HandleFunc("/", handler)

変数名は分かりやすくするために変えていますが、これもHTTPS化で記述が変わることはありません。

SSL/TLS サーバー証明書のリストを初期化する


	tlsConfig := &tls.Config{}
	tlsConfig.Certificates = make([]tls.Certificate, 1)

『SSL/TLS サーバー証明書』という難しい言葉が出てきましたが、簡単に言えば暗号化通信に必要な書類みたいなものです。書類と言っても紙で出来ているわけではなく、ただのファイルデータです。

上記の『&tls.Config{}』では設定(Config)のスライス(データのリストのようなもの)を宣言しています。どうして最初に『&』が付くかと言うと、これでリストのポインタを取得するためです。ポインタについては別の記事でご紹介するとします。

設定のリストというのは『サーバー証明書』を必要なドメインの個数分のリストです。

次の行で、設定リストを指定した個数で初期化しています。『make([]tls.Certificate, 1)』の『make』がスライスの初期化に必要な単語で、次に2つの引数を指定していますね。

1つ目でデータの種類を、2つ目でその個数です。今回は1つのドメイン分を用意するので『1』を指定していますが、ドメインが複数ある場合はその数を指定して下さい。

サーバー証明書と秘密鍵のファイルパスをセットする


var err error
tlsConfig.Certificates[0], err = tls.LoadX509KeyPair("FullChainFilePath", "PrivKeyFilePath")
if err != nil {
    log.Fatal(err)
}

tlsConfig.BuildNameToCertificate()

最初の『var err error』で、関数呼び出し時にエラーが返された場合の格納先を用意しています。

次に『サーバー証明書と中間証明書が含まれたファイル』と『秘密鍵』の場所(パス・アドレス)を指定し、そのファイルを読み込ませてサーバー証明書リストに保存します。

読み込む関数は『tls.LoadX509KeyPair』で、2つの引数を指定し、1つ目に『サーバー証明書と中間証明が含まれたファイル』、2つ目に『秘密鍵』のパスです。

そして、2つのデータが返ってきていますね。『tlsConfig.Certificates[0]』と『err』です。1つ目は読み込まれたデータで、最後に『[0]』となっていますよね。これはリストの1番目を表します。1番目なのにゼロなのか!と思われるかもしれませんが、プログラムではそうなる場合が多いです。

エラーチェック

『if err != nil』で返された『err』が空っぽ(nil)でないかチェックし、何かエラーが発生していれば『log.Fatal(err)』でエラー内容を表示させています。

『Fatal』関数は『Print』関数と同じく文字列を表示させるのですが、『Print』関数は実行させてもプログラムが終了しないのに対し、『Fatal』関数は文字列を表示した後にプログラムを自動的に終了させます。

複数のサーバー証明書を組み立てる

最後の『tlsConfig.BuildNameToCertificate』で複数のサーバー証明書を組み立てています。これで、複数のドメインであっても、1つのサーバー1つのポート番号で対応することが可能になります。

サーバーの設定を行う


ServerSSL := &http.Server{
    Handler:        muxSSL,
    ReadTimeout:    10 * time.Second,
    WriteTimeout:   10 * time.Second,
    MaxHeaderBytes: 1 << 20,
}

ここは基本的にHTTPサーバーの時と同じです。ポート番号の指定が無くなっていますが、後で指定することが可能です。

サーバーがどのようなプロトコルで、ポート番号で通信するのか指定する


listener, err := tls.Listen("tcp", ":443", tlsConfig)
if err != nil {
    log.Fatal(err)
}

1行目の『tls.Listen』関数では『”tcp”』『”:443”』『tlsConfig』の3つを指定していますね。

1つ目の『tcp』とは、これもプロトコルの1つです。Webサーバーのように1対1で通信する場合は『tcp』を指定していて問題ありません。

2つ目の『:443』は接続をするポート番号です。HTTPSでは標準で443番を使用するので、これを指定します。HTTPの場合は80番が標準です。尚、ポート番号1023番以下を使用する場合、プログラムは管理者権限で起動する必要がありますので、起動する際は以下のようなコマンドを使用します。


sudo go run Test.go

先頭に『sudo』を追加することで、管理者権限でコマンドを実行出来るようになります。管理者権限で実行するとシステムを危険に晒す可能性も増しますので、テストする際は他のポート番号を使用するのがオススメです。

最後にサーバーを起動します


err = ServerSSL.Serve(listener)
if err != nil {
    log.Fatal(err)
}

サーバーの設定である『ServerSSL』の『Serve』関数で、引数に『tls.Listen』関数で返ってきた『listener』を指定してサーバーを起動しています。

エラーがあれば、エラー処理をしておきましょう。

おわりに

以上でHTTPSサーバーのコードと解説はおしまいです。

Go言語でシンプルで簡単なHTTPサーバーの作り方』での解説と比べると随分とあっさりとしたものになったので、いきなり当記事を見て学習される方は難しく感じるかもしれませんね。

実際にコードを書いてみて、動作させてみて「このコードはこういう風に動作するのか」と体験してみると分かりやすいかもしれません。動作させることが出来たら、自分なりに改良してみると、手に取るように理解出来るようになると思いますので試してみて下さい。