Go言語によるWebサーバー作成入門 ーnet/httpー3

公開:2020/10/29
更新:2020/11/13
5 min読了の目安(約5100字TECH技術記事

(作成2020.10.29/修正2020.11.13)
前回の記事はこちら

画像の表示の仕方

いきなりですが、これではうまくいきません。main.goと同じフォルダに画像ファイルがある前提です。

main.go NG
package main

import (
	"io"
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", dog)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	http.ListenAndServe(":"+port, nil)
}

func dog(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, `
	<img src="/akita.png">
	`)
}

前述のコードでは、ブラウザに画像ファイルのある場所へ行ってもらって画像データを表示してもらおうとしていました。サーバーに保存されているのと、アドレスバーでアクセスするのでは違うんですよね。そもそも実際に実行されているのはmain.goファイルではなくてコンパイルされた実行ファイルですし。サーバーのディレクトリとURLのrouteは違うものだという風に理解しておいたほうがよいかもしれません。
それでどうするかといいますと、コードの中で画像ファイルにアクセスしてhttp.ResponseWriterを通してhttp.HandleFundを使ってrouteに出力する必要があります。一例ですが、

main.go
package main

import (
	"io"
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", dog)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	http.ListenAndServe(":"+port, nil)
}

func dog(w http.ResponseWriter, req *http.Request) {
	f, err := os.Open("akita.png")
	if err != nil {
		http.Error(w, "file not found", 404)
		return
	}
	defer f.Close()

	io.Copy(w, f)
}

このやり方であれば、Go言語環境のHerokuで画像を表示できます。

http.ServeContent()

画像の表示にio.Copy()を使ってきましたが、http.ServeContent()を使った方がより適切です。

func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker)

main.go
package main

import (
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", dog)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	http.ListenAndServe(":"+port, nil)
}

func dog(w http.ResponseWriter, req *http.Request) {
	f, err := os.Open("akita.png")
	if err != nil {
		http.Error(w, "file not found", 404)
		return
	}
	defer f.Close()

	fi, err := f.Stat() // ファイルのステータスを取ってくる
	if err != nil {
		http.Error(w, "file not found", 404)
		return
	}

	//io.Copy(w, f)
	// MIMEタイプの設定やRangeリスクエストの処理にはこちらが適切
	http.ServeContent(w, req, f.Name(), fi.ModTime(), f)
}

http.ServeFile()

http.ServeFile()というのもあります。これを使うと簡潔さは上ですね。

func ServeFile(w ResponseWriter, r *Request, name string)

main.go
package main

import (
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", dog)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	http.ListenAndServe(":"+port, nil)
}

func dog(w http.ResponseWriter, req *http.Request) {
	http.ServeFile(w, req, "akita.png")
}

これまで画像だけを表示してきましたが、HTMLで出力するコードにします。

main.go
package main

import (
	"io"
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", dog)
	http.HandleFunc("/png", pic)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	http.ListenAndServe(":"+port, nil)
}

func dog(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	io.WriteString(w, `<h1>あきたいぬー</h1><img src="/png">`)
}

func pic(w http.ResponseWriter, req *http.Request) {
	http.ServeFile(w, req, "akita.png")
}

http.FileServer()

FileServerです。前回の記事のServeFileと語順が逆ですがこちらはサーバーを渡します。これまでは1ファイルしか渡せなかったですが、こちらはサーバーを指定するので複数のファイルを渡せます。アクセスするURLが変わります。localhost:8080/dogにアクセスしてください。

main.go
package main
​
import (
        "io"
        "net/http"
        "os"
)func main() {
        http.HandleFunc("/dog", dog)
        http.Handle("/", http.FileServer(http.Dir(".")))
​
        port := os.Getenv("PORT")
        if port == "" {
                port = "8080"
        }
        http.ListenAndServe(":"+port, nil)
}func dog(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        io.WriteString(w, `<h1>あきたいぬー</h1><img src="/akita.png">`)
}

アクセスするURLを変えたのには訳がありまして、http.Handleの第1引数には"/"しか渡せなかったのです。違うものを渡すには少し手間がかかります。

main.go
package main
​
import (
        "io"
        "net/http"
        "os"
)func main() {
        http.HandleFunc("/", dog)
        http.Handle("/png/", http.StripPrefix("/png", http.FileServer(http.Dir("."))))
​
        port := os.Getenv("PORT")
        if port == "" {
                port = "8080"
        }
        http.ListenAndServe(":"+port, nil)
}func dog(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        io.WriteString(w, `<h1>あきたいぬー</h1><img src="/akita.png">`)
}

使ってないのですが「/png」をかませてます。こうすることで、HandleFuncの第1引数にrootが使えました。

index.htmlを設定すればシンプルに書ける

ルートにindex.htmlを設定しておけば、画像の表示はHTMLに任せてしまって、goファイルではディレクトリを渡すだけで書けてしまいます。まぁ、これだとgoの存在意義が全くなくなりますが。

main.go
package main
​
import (
        "log"
        "net/http"
)func main() {
        port := os.Getenv("PORT")
        if port == "" {
                port = "8080"
        }
        log.Fatal(http.ListenAndServe(":"+port, http.FileServer(http.Dir("."))))
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>あきたいぬー</h1>
    <img src="akita.png" alt="あきたいぬ">
</body>
</html>