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

公開:2020/10/30
更新:2020/10/30
8 min読了の目安(約7600字TECH技術記事

(作成 2020.10.30)
前回の記事はこちら

データの送り方、受け取り方

favicon.ico

HandleFuncの第1引数に"/"を渡してindex関数に処理を渡したとき、index関数ではリクエストがどこから来たと認識しているのでしょうか?次のgo文を実行してみてください。

main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", index)
	http.ListenAndServe(":8080", nil)
}

func index(w http.ResponseWriter, req *http.Request) {
	fmt.Println(req.URL.Path)
	fmt.Fprintln(w, "<h1>Hello, World!</h1>")
}

実行結果は、

!なんと、/favicon.icoというところからもリクエストを受けているではないか。実はこの/favicon.icoというのはブラウザのタブの先頭にちっちゃく出ているマークのことです。コード処理ではここからのリクエストというのは不要なので、次の文を入れます。

main.go add
func main() {
	http.HandleFunc("/", index)
	http.Handle("/favicon.ico", http.NotFoundHandler()) // この文を入れる
	http.ListenAndServe(":8080", nil)
}

func NotFoundHandler() Handler

http.NotFoundHandlerは、「404 page not found」を返します。これによって、index関数の中には/favicon.icoからのリクエストは無視されることになります。

URL

次のgo文を実行して、「 http://localhost:8080/?name=ほげほげ 」にアクセスしてみてください。

main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", foo)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func foo(w http.ResponseWriter, req *http.Request) {
	v := req.FormValue("name")
	fmt.Fprintln(w, "あなたの名前は、"+v+"ですね。")
}

POST - formを使う

formをHTMLで書いてResponseWriterに渡します。Requestからnameを受け取ります。

main.go
package main

import (
	"io"
	"net/http"
)

func main() {
	http.HandleFunc("/", foo)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func foo(w http.ResponseWriter, req *http.Request) {
	v := req.FormValue("name")
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	io.WriteString(w, `
	<form method="POST">
	 <input type="text" name="name">
	 <input type="submit">
	</form>
	<br>`+v)
}

GET - formを使う

POSTをGETに変えるとどうなるでしょうか。

main.go
package main

import (
	"io"
	"net/http"
)

func main() {
	http.HandleFunc("/", foo)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func foo(w http.ResponseWriter, req *http.Request) {
	v := req.FormValue("name")
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	io.WriteString(w, `
	<form method="GET">
	 <input type="text" name="name">
	 <input type="submit">
	</form>
	<br>`+v)
}

URLが変わります。

POSTではBodyに含めて文字を送れるので表面上出てきませんが、GETは受け取り専用であるため、データを送るためにはURLを使わざるを得ません。パスワードはGETで送らないでください。

formはHTMLで用意する(テンプレート)

form部分はHTMLで書いた方がよいですね。エディタも対応してるし。プログラム部分と表示部分は切り分けるのがよいと思います。

main.go
package main

import (
	"html/template"
	"log"
	"net/http"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseGlob("templates/*"))
}

type person struct {
	Pname string
	Plike bool
}

func main() {
	http.HandleFunc("/", foo)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func foo(w http.ResponseWriter, req *http.Request) {

	n := req.FormValue("name")
	l := req.FormValue("like") == "on"

	err := tpl.ExecuteTemplate(w, "index.gohtml", person{n, l})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError) // 500
		log.Fatalln(err)
	}
}
index.html
{{template "header"}}

<form method="POST">
    <label for="">なまえ</label>
    <input type="text" id="name" name="name">
    <br>
    <label for="sub">好きか?</label>
    <input type="checkbox" id="like" name="like">
    <br>
    <input type="submit">
</form>

<br>

<h1>名前: {{.Pname}}</h1> <!-- person 構造体のフィールド -->
<h1>好きか?: {{.Plike}}</h1> <!-- person 構造体のフィールド -->

{{template "footer"}}

headerとfooterは省略(こちらご参照

ファイルをアップロードして表示する(テキストファイル)

http.RequestにFormFile()というのがあってこれを使います。

func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)

main.go
package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
)

func main() {
	http.HandleFunc("/", foo)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func foo(w http.ResponseWriter, req *http.Request) {

	var s string
	fmt.Println(req.Method) // POSTかGETかを出力してみる
	if req.Method == http.MethodPost {

		// open
		f, _, err := req.FormFile("q")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer f.Close()

		// read
		bs, err := ioutil.ReadAll(f)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		s = string(bs)
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	io.WriteString(w, `
	<form method="POST" enctype="multipart/form-data">
	<input type="file" name="q">
	<input type="submit">
	</form>
	<br>`+s)
}
00text.txt
これはテストです。
これはテストです。

ファイルを選択して、

送信するとこうなります。改行は無視されてしまいますね。

POSTかGETかを吐き出しているので動きを確かめてみてください。

画像ファイルを送信するとこうなります。化けます。

ファイルをアップロードして格納する

userディレクトリを作成して次のような構成にしてください。

/main.go
|--/templates/index.gohtml(, header.gohtml, footer.gohtml)
|--/user/

アップロードしたファイルをサーバーに格納するコードを追加します。格納は、空ファイルを作成してそこにアップロードファイルの情報を書き込むことで達成しています。

main.go
package main

import (
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseGlob("templates/*"))
}

func main() {
	http.HandleFunc("/", foo)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func foo(w http.ResponseWriter, req *http.Request) {

	var s string
	if req.Method == http.MethodPost {

		// open
		f, h, err := req.FormFile("q")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer f.Close()

		// read
		bs, err := ioutil.ReadAll(f)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		s = string(bs)

		// ここの段落を追加した store onto server
		dst, err := os.Create(filepath.Join("./user/", h.Filename))
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer dst.Close()

		_, err = dst.Write(bs) // ここで書き込んでいる
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	tpl.ExecuteTemplate(w, "index.gohtml", s)
}
templates/index.gohtml
{{template "header"}}

<form method="POST" enctype="multipart/form-data">
    <label for="idf">アップロードファイル選択</label>
    <input type="file" id="idf" name="q">
    <br>
    <input type="submit">
</form>

{{if .}}
<h1>アップロードしたファイルの内容:</h1>
{{.}}
{{end}}

{{template "footer"}}

実行すると、

送信すると、

ファイルが格納されている。

画像ファイルのHTML上の表示は、バッファデータをstringにキャストしているため文字化けしますが、ファイル格納の方はstringにキャストせずWrite関数で書き込んでいるので、画像ファイルでもうまくいきます。