32. Server web pertamamu — Solusi PR
File solusi .go ada di
exercises/32/homework/solutions/.
Soal 1 — Tiga halaman
Soal. Tiga rute untuk GET, dan 404
untuk yang lainnya.
Cara berpikir. Satu handler untuk setiap halaman,
dan satu baris di main untuk setiap handler. Halaman depan
butuh "GET /{$}", karena "GET /" akan cocok
dengan semua path, dan /contact/extra akan mendapat halaman
depan. "GET /contact" tidak punya garis miring di akhir,
jadi hanya cocok dengan path itu persis.
Solusi yang dikerjakan.
package main
import (
"fmt"
"net/http"
)
func handleHome(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "This is the home page.")
}
func handleAbout(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "This server has three pages.")
}
func handleContact(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Write to Keiko at the front desk.")
}
func main() {
http.HandleFunc("GET /{$}", handleHome)
http.HandleFunc("GET /about", handleAbout)
http.HandleFunc("GET /contact", handleContact)
fmt.Println("Server running at http://localhost:8080")
fmt.Println("Press Ctrl+C to stop it.")
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
fmt.Println("Server stopped:", err)
}
}Hasilnya:
curl.exe -i http://localhost:8080/contact/extra
HTTP/1.1 404 Not Found
404 page not found
curl.exe -i -X POST http://localhost:8080/about
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD
Method Not Allowed
Kesalahan umum.
- Menulis
"GET /"untuk halaman depan. Maka/nothingmendapat status 200. - Menulis
"GET /contact/", dengan garis miring di akhir. Itu cocok dengan semua yang ada di bawahnya, jadi/contact/extramendapat halaman itu. - Menulis
handleAbout()di barishttp.HandleFunc. Itu memanggil fungsinya dan memberikan hasilnya. Berikan namanya, tanpa().
Soal 2 — Halaman HTML
Soal. Halaman dengan judul, paragraf, dan daftar.
Cara berpikir. Halamannya tidak berubah, jadi
konstanta bisa menyimpannya. Halaman itu punya beberapa baris dan
beberapa karakter ", jadi raw string cocok. Atur header
sebelum menulis body.
Solusi yang dikerjakan.
package main
import (
"fmt"
"net/http"
)
const homePage = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Things I like</title>
</head>
<body>
<h1>Things I like</h1>
<p>Here are three of them.</p>
<ul>
<li>Fried rice</li>
<li>Long walks</li>
<li>Small programs</li>
</ul>
</body>
</html>
`
func handleHome(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, homePage)
}
func main() {
http.HandleFunc("GET /{$}", handleHome)
fmt.Println("Server running at http://localhost:8080")
fmt.Println("Press Ctrl+C to stop it.")
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
fmt.Println("Server stopped:", err)
}
}Kesalahan umum.
- Memakai
fmt.Fprintf(w, homePage). Itu berjalan sampai halamannya memuat%, yang dianggapFprintfsebagai awal verb.fmt.Fprinttidak punya masalah itu. - Mengatur header sesudah
fmt.Fprint. Header itu diabaikan, tanpa pesan. - Melewatkan
charset=utf-8, sehingga teks sepertiébisa tampil sebagai karakter aneh.
Soal 3 — Kode status
Soal. Tiga rute, masing-masing dengan statusnya sendiri.
Cara berpikir. Yang pertama tidak melakukan apa-apa yang khusus, karena 200 adalah yang kamu dapat kalau tidak mengatur status. Dua lainnya adalah dua fungsi pembantu, masing-masing satu baris.
Solusi yang dikerjakan.
package main
import (
"fmt"
"net/http"
)
func handleHome(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Try /ok, /gone and /locked.")
}
func handleOK(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "All good.")
}
func handleGone(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}
func handleLocked(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Locked.", http.StatusForbidden)
}
func main() {
http.HandleFunc("GET /{$}", handleHome)
http.HandleFunc("GET /ok", handleOK)
http.HandleFunc("GET /gone", handleGone)
http.HandleFunc("GET /locked", handleLocked)
fmt.Println("Server running at http://localhost:8080")
fmt.Println("Press Ctrl+C to stop it.")
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
fmt.Println("Server stopped:", err)
}
}Hasilnya:
curl.exe -i http://localhost:8080/ok
HTTP/1.1 200 OK
All good.
curl.exe -i http://localhost:8080/gone
HTTP/1.1 404 Not Found
404 page not found
curl.exe -i http://localhost:8080/locked
HTTP/1.1 403 Forbidden
Locked.
Kesalahan umum.
- Menulis
403sebagai gantihttp.StatusForbidden. Itu berjalan, tetapi namanya memberi tahu pembaca berikutnya apa artinya. - Tertukar urutan
http.Error. Urutannya writer, lalu teks, lalu status.
Tantangan — Situs kecil
Soal. Fungsi pembantu yang menulis seluruh halaman, dipakai oleh beberapa handler, dan halaman 404-mu sendiri.
Cara berpikir. Halamannya selalu sama kecuali dua
bagian, jadi bisa berupa string format dengan %s di
dalamnya, yang diisi dengan fmt.Fprintf. Fungsi pembantu
itu melakukan tiga langkah sesuai urutan keluarnya response: header,
status, body. Itulah sebabnya status menjadi salah satu
parameternya: handler 404 tidak bisa mengatur status lebih dulu lalu
memanggil fungsi yang mengatur header.
"GET /" menangkap semua path GET yang tidak cocok dengan
yang lebih spesifik, jadi itulah tempat untuk halaman 404. Di sini pola
itu lebih baik daripada "/": metode lain pada
/about tetap mendapat 405, karena router melihat bahwa path
itu ada untuk GET.
Solusi yang dikerjakan.
package main
import (
"fmt"
"net/http"
)
const pageFormat = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>%s</title>
</head>
<body>
<h1>%s</h1>
<p>%s</p>
<p><a href="/">Home</a> | <a href="/about">About</a> | <a href="/games">Games</a></p>
</body>
</html>
`
// writePage sends a whole page. The title and the body must be text from this
// program, never text from a visitor, because they go into the HTML as they are.
func writePage(w http.ResponseWriter, status int, title string, body string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(status)
fmt.Fprintf(w, pageFormat, title, title, body)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
writePage(w, http.StatusOK, "Home", "Welcome to a small site.")
}
func handleAbout(w http.ResponseWriter, r *http.Request) {
writePage(w, http.StatusOK, "About", "This site is one Go file.")
}
func handleGames(w http.ResponseWriter, r *http.Request) {
writePage(w, http.StatusOK, "Games", "Number Guess and Monster Battle.")
}
func handleNotFound(w http.ResponseWriter, r *http.Request) {
writePage(w, http.StatusNotFound, "Not found", "There is no page at this address.")
}
func main() {
http.HandleFunc("GET /{$}", handleHome)
http.HandleFunc("GET /about", handleAbout)
http.HandleFunc("GET /games", handleGames)
http.HandleFunc("GET /", handleNotFound)
fmt.Println("Server running at http://localhost:8080")
fmt.Println("Press Ctrl+C to stop it.")
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
fmt.Println("Server stopped:", err)
}
}Komentar pada writePage itu penting. Fungsi ini menaruh
argumennya ke dalam HTML apa adanya. Itu tidak masalah untuk teks di
dalam programmu sendiri, dan tidak aman untuk teks dari pengunjung. Bab
berikutnya menunjukkan alasannya, dan Bab 34 menunjukkan alat yang
tepat.
Kesalahan umum.
- Memanggil
w.WriteHeaderlebih dulu lalu mengaturContent-Typedi fungsi pembantu. Header-nya diabaikan. - Menulis
w.WriteHeader(http.StatusNotFound)sesudahfmt.Fprintf. Halaman keluar dengan status 200. - Mendaftarkan halaman 404 sebagai
"/". MakaPOST /aboutmendapat halaman 404-mu, bukan 405.