【解答】Go言語 初心者向けハンズオン

※これは解答です。どうしても課題が解けない場合、ご利用ください。

# 練習問題 #1: FizzBuzz APIを作ろう

```go
package main

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

	"github.com/julienschmidt/httprouter"
)

func FizzBuzz(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	num, err := strconv.Atoi(p.ByName("num"))
	if err != nil || num < 1 {
		http.Error(w, fmt.Sprintf("%d Bad Request", http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	var str string
	for i := 1; i <= num; i++ {
		if i%15 == 0 {
			str += fmt.Sprintf("%d: %s\n", i, "FizzBuzz!")
		} else if i%5 == 0 {
			str += fmt.Sprintf("%d: %s\n", i, "Buzz")
		} else if i%3 == 0 {
			str += fmt.Sprintf("%d: %s\n", i, "Fizz")
		} else {
			str += fmt.Sprintf("%d:\n", i)
		}
	}
	fmt.Fprintf(w, str)
}

func main() {
	router := httprouter.New()
	router.GET("/FizzBuzz/:num", FizzBuzz)

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


# 練習問題 #2: プロフィール取得APIを作ろう

```go
package main

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

	"github.com/julienschmidt/httprouter"
)

type Profile struct {
	Name          string   `json:"name"`
	Age           int      `json:"age"`
	Gender        string   `json:"gender"`
	FavoriteFoods []string `json:"favorite_foods"`
}

var bob Profile = Profile{
	Name:          "Bob",
	Age:           25,
	Gender:        "Man",
	FavoriteFoods: []string{"Hamburger", "Cookie", "Chocolate"},
}

var alice Profile = Profile{
	Name:          "Alice",
	Age:           24,
	Gender:        "Woman",
	FavoriteFoods: []string{"Apple", "Orange", "Melon"},
}

func GetProfile(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	name := p.ByName("name")

	var resProfile Profile
	if name == "Bob" {
		resProfile = bob
	} else if name == "Alice" {
		resProfile = alice
	} else {
		http.Error(w, fmt.Sprintf("%d Not Found", http.StatusNotFound), http.StatusNotFound)
		return
	}

	bytes, err := json.Marshal(resProfile)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}
	fmt.Fprintf(w, string(bytes))
}

func main() {
	router := httprouter.New()
	router.GET("/Profile/:name", GetProfile)

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

# 練習問題 #3: プロフィール保存APIを作ろう

```go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

type Profile struct {
	Name          string   `json:"name"`
	Age           int      `json:"age"`
	Gender        string   `json:"gender"`
	FavoriteFoods []string `json:"favorite_foods"`
}

var savedProfiles map[string]Profile = map[string]Profile{}

func PostProfile(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var reqProfile Profile
	err = json.Unmarshal(body, &reqProfile)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if reqProfile.Name == "" {
		http.Error(w, "name is required.", http.StatusBadRequest)
		return
	}

	savedProfiles[reqProfile.Name] = reqProfile
	fmt.Fprintf(w, fmt.Sprintf("%d Created", http.StatusCreated))
}

func main() {
	router := httprouter.New()
	router.POST("/Profile", PostProfile)

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

# 練習問題 #4: プロフィール取得APIを拡張しよう

```go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

type Profile struct {
	Name          string   `json:"name"`
	Age           int      `json:"age"`
	Gender        string   `json:"gender"`
	FavoriteFoods []string `json:"favorite_foods"`
}

var savedProfiles map[string]Profile = map[string]Profile{}

func PostProfile(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var reqProfile Profile
	err = json.Unmarshal(body, &reqProfile)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if reqProfile.Name == "" {
		http.Error(w, "name is required.", http.StatusBadRequest)
		return
	}

	savedProfiles[reqProfile.Name] = reqProfile
	fmt.Fprintf(w, fmt.Sprintf("%d Created", http.StatusCreated))
}

func GetProfile(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	name := p.ByName("name")
	resProfile, found := savedProfiles[name]

	if !found {
		http.Error(w, fmt.Sprintf("%d Not Found", http.StatusNotFound), http.StatusNotFound)
		return
	}

	bytes, err := json.Marshal(resProfile)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}
	fmt.Fprintf(w, string(bytes))
}

func main() {
	router := httprouter.New()
	router.POST("/Profile", PostProfile)
	router.GET("/Profile/:name", GetProfile)

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

# 練習問題 #5: APIクライアントを作ろう

**get_client.go**

```go
package main

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

func main() {
	name := flag.String("name", "", "Name")
	flag.Parse()
	resp, err := http.Get(fmt.Sprintf("http://localhost:8080/Profile/%s", *name))
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(bytes))
}
```

**post_client.go**

```go
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

type Profile struct {
	Name          string   `json:"name"`
	Age           int      `json:"age"`
	Gender        string   `json:"gender"`
	FavoriteFoods []string `json:"favorite_foods"`
}

func main() {
	profile := parseArgs()
	fmt.Printf("%+v\n", profile)

	jsonStr, err := json.Marshal(profile)
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest("POST", "http://localhost:8080/Profile", bytes.NewBuffer([]byte(jsonStr)))
	if err != nil {
		log.Fatal(err)
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)

	fmt.Println(string(body))
}

func parseArgs() Profile {
	name := flag.String("name", "", "Name")
	age := flag.Int("age", 0, "Age")
	gender := flag.String("gender", "", "Gender")
	foods := flag.String("foods", "", "Favorite foods")
	flag.Parse()

	return Profile{
		Name:          *name,
		Age:           *age,
		Gender:        *gender,
		FavoriteFoods: strings.Split(*foods, " "),
	}
}
```

# 練習問題 #6: HTMLを返そう

**index.html.tpl**

```html
<html>
<body>
	<h1>Hello golang!</h1>
	<div><a href="/profile/Alice">Alice</a></div>
	<div><a href="/profile/Bob">Bob</a></div>
</body>
</html>
```

**profile.html.tpl**

```html
<html>
<body>
	<h1>Hello golang!</h1>

	<div>Name: {{ .Name }}</div>
	<div>Age: {{ .Age }}</div>
	<div>Gender: {{ .Gender }}</div>
	<div>Favorite Foods: {{ .FavoriteFoods }}</div>
</body>
</html>
```

**html_server.go**

```go
package main

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

	"github.com/julienschmidt/httprouter"
)

type Profile struct {
	Name          string   `json:"name"`
	Age           int      `json:"age"`
	Gender        string   `json:"gender"`
	FavoriteFoods []string `json:"favorite_foods"`
}

var bob Profile = Profile{
	Name:          "Bob",
	Age:           25,
	Gender:        "Man",
	FavoriteFoods: []string{"Hamburger", "Cookie", "Chocolate"},
}

var alice Profile = Profile{
	Name:          "Alice",
	Age:           24,
	Gender:        "Woman",
	FavoriteFoods: []string{"Apple", "Orange", "Melon"},
}

func Index(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	t := template.Must(template.ParseFiles("templates/index.html.tpl"))
	err := t.ExecuteTemplate(w, "index.html.tpl", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func GetProfile(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	name := p.ByName("name")

	var resProfile Profile
	if name == "Bob" {
		resProfile = bob
	} else if name == "Alice" {
		resProfile = alice
	} else {
		http.Error(w, fmt.Sprintf("%d Not Found", http.StatusNotFound), http.StatusNotFound)
		return
	}

	t := template.Must(template.ParseFiles("templates/profile.html.tpl"))
	err := t.ExecuteTemplate(w, "profile.html.tpl", resProfile)
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/profile/:name", GetProfile)

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