0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go言語(プログラミング)入門メモ㉚-簡単Webアプリケーション-

Posted at

テキストを編集して表示するアプリケーションを作成する

web上にテキストを表示して編集できる簡単なWebアプリケーションを作成する。

テキスト表示ページ
image.png

テキスト編集ページ
image.png

①Webページを管理する構造体を作る
→構造体Page(フィールドにはstring型のTitleとbyte配列のBodyを設定する)

type Page struct {
	Title string
	Body  []byte
}

②構造体Pageにsaveメソッド(ページの情報をテキストファイルに保存する動作のイメージ)

func (p *Page) save() error {
	filename := p.Title + ".txt"
	return os.WriteFile(filename, p.Body, 0600)
}

メソッドの概要:

saveメソッドは、Page構造体のデータをファイルに保存するためのメソッド。

saveメソッドはPage構造体に関連付けられており、Page構造体のインスタンス(ページオブジェクト)に対して呼び出される。

戻り値はerror型で、エラーが発生した場合にエラーメッセージを返す。
→os.Writefile関数の戻り値は nil or error なのでerror型をsaveメソッドの戻り値をerror型に指定する必要がある。
(nilもerror型の一種)

ポインタレシーバー*Page:

ポインタとは、変数やデータの「アドレス」を指し示すもの。つまり、データそのものではなく、その場所を持つ。

なぜポインタを使うのか:

ポインタを使うことで、データのコピーを避け、メモリ効率を向上させることができ、直接元のデータを操作するため、データの一貫性も保てる。

filename := p.Title + ".txt":

ページのタイトルに.txtを付けて、ファイル名を生成する。
例えば、タイトルが"TestPage"の場合、ファイル名は"TestPage.txt"になる。

return os.WriteFile(filename, p.Body, 0600):

os.WriteFile関数を使用して、生成したファイル名にPageの内容(Bodyフィールド)を書き込みます。

filename:

保存先のファイル名。

p.Body:

ファイルに書き込むデータ(ページの内容)。

0600:

ファイルのパーミッション(アクセス権)を指定している。0600は、ファイルの所有者が読み書きできる権限を表す。
書き込み操作が成功すればnilを、失敗すればエラーメッセージを返す。

③main関数内で、変数p1に構造体Pageを代入して初期化する。また、フィールドのTitleとBodyに値を入れておく。
その後、saveメソッドを実行する。

func main() {
    p1 := &Page{Title: "test", Body: []byte("This is a sample Page")}
    p1.save()
}

実行すると、「test.txt」という名前のファイルが保存されて「This is a sample Page」と書き込まれている。

④loadPage関数を作成する
テキストファイルからページの情報を読み込む処理を作成する。
loadPage関数の引数をstring型のtitleとし、返り値には*Pageとerrorを返す。
変数filenameに引数titleに「.txt」を加えてファイル名を代入し、os.ReadFile関数で読み込んだ内容を変数bodyに代入する。
エラーハンドリングをして、TitleとBodyに値を代入した構造体Pageのアドレスと、エラーがないのでnilを返り値として返す。

→要約すると、この関数は、指定されたタイトルのテキストファイルを読み込み、その内容をPage構造体として返す関数。エラーが発生した場合は、nilとエラーを返す。

func loadPage(title string) (*Page, error) {
	filename := title + ".txt"
	body, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &Page{Title: title, Body: body}, nil
}

⑤main関数でloadPage関数で読み込んだ内容を変数p2に代入し、p2.Bodyの内容をstring型にして表示する

func main() {
    p1 := &Page{Title: "test", Body: []byte("This is a sample Page")}
    p1.save()

    p2,_ := loadPage(p1.Title)
    fmt.Println(string(p2.Body))
}

⑥URLの登録と、webサーバの立ち上げ
http.HandleFunc関数でURLの登録を行う。
第1引数に「/view/」を指定し、第2引数でこのURLにアクセスが来た時に処理を行うハンドラーの関数(ここでは、viewHandler)を指定する。
webサーバの立ち上げはhttp.ListenAndServe関数で行う。第1引数に「:8080」を指定しwebサーバを立ち上げるポート番号を決め、第2引数ではハンドラー(HTTPリクエストに応答するインターフェース)を指定する。今回は、デフォルトのハンドラーを指定したいのでnilにする。

func main() {
	http.HandleFunc("/view/", viewHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

次にviewHandler関数を定義する。
関数の引数はそれぞれ、
w http.ResponseWriter: レスポンスを書き込むためのインターフェース。

r *http.Request: リクエストの詳細を持つ構造体へのポインタ。
この引数rに対して「r.URL.Path」のように書くと、URLのパスの情報が取得できる。
例えば。「localhost:8080/view/test」というURLの場合、「view/test」の部分が取得できる。
さらに「test」の部分の文字列を取り出すために「r.URL.Path[len("/view/"):]」と書いて「/view/」の文字列の長さの数よりも後ろの文字列を取得して、変数titleに格納する。
その後、loadPage関数を呼び出して、指定されたタイトルのページを読み込む。
次にfmt.Fprintf関数で、第2引数以降で指定したフォーマットの文字列を、http.ResponseWriterの変数wに書き込み、HTTPレスポンスとしてwebページに表示するHTMLを作る。
ここでは、loadPage関数で酔いこんだページのTitleとBodyの情報を渡して表示させている。

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/view/"):]
	p, _ := loadPage(title)
	fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>",p.Title, p.Body)
}

このコードを実行するとwebサーバが立ち上がる。

HTMLテンプレートを利用する

上記のコードでは、viewHandler関数の中でfmt.Fprintln関数を使い、直接HTMLを書いていた。もっとHTMLが長くなったとき、別のファイルに切り出して書くことが現実的。
そこで、テンプレートとなるHTMLファイルを作り、text/templateパッケージを使って読み込みを行う。
先ほど作成した「/view/」のURLで表示されるviewページと「/edit/」のURLでviewページの本文の内容を編集できるeditページの2つについて、テンプレートを使用して表示してみる。

viewページの作成。

同じディレクトリに「view.html」を作成する。
次に、vierHandler関数の処理を変更する。fmt.Fprint関数でHTMLを作成するのではなく、renderTemplate関数という独自の関数を作成し、変数wと文字列「view」、変数pを渡してHTTPレスポンスを作成するようにする。

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/view/"):]
	p, _ := loadPage(title)
	renderTemplate(w, "view", p)  //renderTemplateを呼び出す処理に変更
}

renderTemplate関数では、template.ParseFiles関数でテンプレートとなるHTMLファイルを作成し、テンプレートを読み込む読み込んだテンプレートを代入した変数tでExcuteメソッドを実行し、変数pに格納されたWebページの情報をhttp.ResponseWriterに書き込む

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	t, _ := template.ParseFiles(tmpl + ".html")
    t.Excute(w, p)
}

次に、view.htmlを書く。

<h1>{{.Title}}</h1>

と書くとExcuteメソッドに渡した変数pのTitleをh1要素に入れることができる。
image.png
次に、後ほど作成するeditページのリンク(a要素)を作成する。

<p>[<a href="/edit/{{Title}}">Edit</a>]</p>

と書くと、Titleの文字列を含めた「/edit/{{.Title}}」というURLへのリンクを作成できる。
Titleの値が「test」であれば、「/edit/test」というページへのリンクが作れる。
byte配列であるBodyの内容を表示するには、

<div>{{printf "%s" .Body}}</div>

と書いて文字列に変換する。

    <h1>{{.Title}}</h1>
    <p>[<a href="/edit/{{.Title}}">Edit</a>]</p>
    <div>{{printf "%s" .Body}}</div>

editページを作成する

次にeditのページの処理を作成する。main関数で、http.HandleFunc関数の処理を追加し、「/edit/」のURLでeditHandler関数を呼び出す。

func main() {
	http.HandleFunc("/view/", viewHandler)
	http.HandleFunc("/edit/", editHandler)  //「/edit/」のURLを登録
    log.Fatal(http.ListenAndServe(":8080", nil))
}

次にedithandler関数を作成する。loadPage関数の処理でエラーハンドリングを行い、ページが読み込めなかった場合は、新しくPage構造体を作成して変数pに代入する。

func editHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/edit/"):]
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

edit.htmlを作成する。h1要素は「Edititng{{.Title}}」を書いておく。
続いて、編集しな内容を送信するためのform要素、webページ上で入力可能な枠を作るtextarea要素、そして入力内容を送信するためのボタンとしてinput要素を作成する。

image.png

まずはform要素を作る。actionは「/save/{{.Title}}」、methodはprint「Post」とする。これは、form要素の処理を送信するときに、「/save{{.Title}}」のURLに対してPOSTリクエストを送信するという意味。このPOSTリクエストの処理は後で作成する。
textarea要素には、loadPage関数で読み込んだ変数pのBodyの内容を{{printf "%s" .Body}}で表示する。このとき、name属性は「body」とし、
「rws="20"」と「cols="80"」でtextarea要素の大きさを指定する。
最後にinput要素を、「type="submit"」と「value="Save"」を指定して作成する。

<h1>Editing {{.Title}}</h1>
    <form action="/save/{{.Title}}" method="POST">
        <div>
            <textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea>
        </div>
        <div>
            <input type="submit" value="Save">
        </div>
    </form>

この状態でコードを実行し、「localhost:8080/view/test」にアクセスしてみると、test.txtの内容を読み込んで、view.htmlのテンプレートを基にページを表示している。
image.png

[Edit]のリンクをクリックすると「http:/localhost:8080/edit/test」に遷移する。
image.png

#3 Webページからの入力内容をファイルに保存する
editページで「Save」のボタンを押したときの処理として、save関数を作る。
main関数でhttp.HandleFunc関数の処理を追加して、「/save/」のURLでsaveHandler関数を呼び出すようにする。

func main() {
	http.HandleFunc("/view/", viewHandler)
	http.HandleFunc("/edit/", editHandler)
	http.HandleFunc("/save/", saveHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

続いて、saveHandler関数の処理を作成する。
edit.htmlで入力したform要素の内容は、typeがsubmitのinput要素であるボタンが押されたときに送信され、引数の「r *http.Request」に格納されている。
textarea要素のname属性に「body」と指定しているため、textarea要素の内容はFormValueメソッドを使って取得できる。
取得した情報を変数bodyに格納したらPage構造体のフィールドに値を入れて変数pに代入する。
このPageの情報をsaveメソッドでファイルに保存する。エラーハンドリングを行い、もしエラーが発生した場合はhttp.Error関数を実行する。この時http.StatusInternalServerErrorを指定することで、HTTPステータスコードの500を返す。
エラーがなければ、http.Redirect関数で「"/view/"+title」のURLに遷移させる。HTTPステータスコードは、http.StatusFoundで302を返す。

func saveHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/save/"):]
	body := r.FormValue("body")
	p := &Page{Title: title, Body: []byte(body)}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

学習に使用した教材

・『入門】Golang基礎入門 + 各種ライブラリ + 簡単なTodoWebアプリケーション開発(Go言語)』M.A EduTech
https://www.udemy.com/course/golang-webgosql/?utm_medium=udemyads&utm_source=bene-msa&utm_campaign=responsive&utm_content=top-1&utm_term=general&msclkid=81e2f24a32cc185d275d953d60760226&couponCode=NEWYEARCAREERJP

・『シリコンバレー一流プログラマーが教える Goプロフェッショナル大全』酒井 潤 (著)
https://www.amazon.co.jp/%E3%82%B7%E3%83%AA%E3%82%B3%E3%83%B3%E3%83%90%E3%83%AC%E3%83%BC%E4%B8%80%E6%B5%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E6%95%99%E3%81%88%E3%82%8B-Go%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E5%A4%A7%E5%85%A8-%E9%85%92%E4%BA%95-%E6%BD%A4/dp/4046070897

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?