32. Server web pertamamu
Server di Bab 31 memberikan jawaban yang sama untuk semua path. Server sungguhan punya jawaban berbeda untuk setiap path, dan menolak apa yang tidak bisa dia lakukan. Bab ini menambahkan rute, halaman HTML, header, dan kode status.
Rute
Rute menghubungkan pola dengan handler. Ini
exercises/32/01-routes.go:
// Chapter 32 example: several routes with Go 1.22 patterns.
// Run with: go run exercises/32/01-routes.go
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 was written in Go.")
}
func main() {
http.HandleFunc("GET /{$}", handleHome)
http.HandleFunc("GET /about", handleAbout)
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)
}
}Jalankan dari go-work. Dengan curl.exe -i,
kamu bisa memeriksa setiap rute. Mulai dari sini, buku ini hanya
menampilkan baris pertama dan body, dan melewatkan header yang tidak
penting untuk contohnya.
curl.exe -i http://localhost:8080/
HTTP/1.1 200 OK
This is the home page.
curl.exe -i http://localhost:8080/about
HTTP/1.1 200 OK
This server was written in Go.
curl.exe -i http://localhost:8080/nothing
HTTP/1.1 404 Not Found
404 page not found
Kamu tidak menulis kode apa pun untuk 404 itu. Router mengirimnya ketika tidak ada pola yang cocok.
Membaca pola
Go 1.22 menambahkan metode dan wildcard pada pola.
"GET /about" punya dua bagian, dengan satu spasi di
antaranya:
GETberarti rute itu hanya menjawab metode ini. Rute itu juga menjawabHEAD, sejenisGETyang hanya meminta header./aboutadalah path-nya.
Kirim metode lain ke path itu, dan Go memberikan status 405 dengan
sendirinya. -X POST meminta curl.exe memakai
POST:
curl.exe -i -X POST http://localhost:8080/about
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD
Method Not Allowed
Allow adalah header yang mendaftar apa yang seharusnya
berhasil.
"GET /{$}" tampak aneh, dan ada alasannya. Pola
yang berakhir dengan / cocok dengan semua yang ada di
bawahnya. Jadi "GET /" saja juga akan menangkap
/nothing, dan menjawab dengan halaman depan dan status 200.
{$} berarti "dan berhenti di sini", jadi pola itu cocok
persis dengan /.
Ketika lebih dari satu pola cocok, yang paling spesifik yang menang,
jadi urutan baris http.HandleFunc tidak penting.
Dua kesalahan ini tidak menghentikan program, jadi sulit ditemukan:
| Yang kamu tulis | Yang terjadi |
|---|---|
"GET/about" |
Tanpa spasi, Go membaca GET sebagai nama komputer.
/about memberikan 404. |
"get /about" |
Metode membedakan huruf besar dan kecil. Browser mengirim
GET, dan mendapat 405. |
Mendaftarkan pola yang sama dua kali membuat program crash begitu dimulai:
panic: pattern "GET /about" (registered at C:\go-work\exercises\32\01-routes.go:22) conflicts with pattern "GET /about" (registered at C:\go-work\exercises\32\01-routes.go:21):
GET /about matches the same requests as GET /about
Buka exercises/32/01-routes.go. Tambahkan rute
GET /hours yang menjawab dengan jam bukamu. Lalu coba
/hours/, dengan garis miring di akhir, dan lihat
statusnya.
Dua parameter
Handler selalu punya dua parameter yang sama.
r *http.Request adalah apa yang dikirim
pengunjung. Sejauh ini kamu sudah memakai r.Method dan
r.URL.Path. Bab 33 membaca sisanya.
w http.ResponseWriter adalah sisimu
dalam percakapan ini. Dia adalah interface dengan tiga metode:
w.Write, yang mengirim sebagian body.fmt.Fprintfmemanggilnya untukmu.w.Header(), yang memberimu header, supaya kamu bisa mengaturnya.w.WriteHeader(code), yang mengatur status. Kalau tidak dipanggil, statusnya 200.
Urutannya penting, karena response keluar dari atas ke bawah: status,
header, lalu body. Jadi atur header dulu, lalu status, lalu
tulis body. Begitu body dimulai, sudah terlambat. Memanggil
WriteHeader sesudah menulis tidak mengubah status, dan Go
mengeluh di terminal:
2026/11/05 01:33:24 http: superfluous response.WriteHeader call from main.handleCreated (03-status-codes.go:29)
Mengatur header sesudah menulis diabaikan begitu saja, tanpa pesan apa pun.
Mengirim HTML
Browser menampilkan teks biasa apa adanya. Untuk mendapatkan judul
atau tautan, kirim HTML, yaitu teks dengan tag di dalam
kurung sudut. Buku ini hanya mengandaikan sedikit:
<h1> adalah judul besar, <p>
adalah paragraf, <a href="..."> adalah tautan, dan
<ul> serta <li> membuat
daftar.
Browser perlu diberi tahu bahwa dia menerima HTML. Itulah gunanya
header Content-Type. Ini bagian penting dari
exercises/32/02-html-page.go:
const homePage = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
</head>
<body>
<h1>Welcome</h1>
<p>This page came from a Go string.</p>
<p><a href="/about">About this server</a></p>
</body>
</html>
`
func handleHome(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, homePage)
}Halamannya berupa raw string di antara backtick, dari Bab 12, supaya
bisa terdiri dari banyak baris dan memuat ".
fmt.Fprint menuliskannya tanpa format dan tanpa akhir baris
tambahan.
charset=utf-8 mengatakan bagaimana karakter disimpan,
supaya teks seperti é tampil dengan benar. Selalu
sertakan.
Kalau kamu melewatkan header itu, Go melihat awal body dan menebak. Di sini tebakannya benar. Tetapi tebakan bukan sesuatu yang bisa diandalkan, jadi aturlah sendiri.
Buka exercises/32/02-html-page.go. Jalankan, dan klik
tautannya. Ubah text/html menjadi text/plain
di satu handler, lalu mulai ulang. Apa yang ditampilkan browser
sekarang?
Kode status
exercises/32/03-status-codes.go punya satu handler untuk
setiap cara mengatur status:
func handleOld(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}
func handlePrivate(w http.ResponseWriter, r *http.Request) {
http.Error(w, "You may not see this page.", http.StatusForbidden)
}
func handleBroken(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Something went wrong on the server.", http.StatusInternalServerError)
}
func handleCreated(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
fmt.Fprintln(w, "This reply has the status 201 Created.")
}Hasilnya:
curl.exe -i http://localhost:8080/old
HTTP/1.1 404 Not Found
404 page not found
curl.exe -i http://localhost:8080/private
HTTP/1.1 403 Forbidden
You may not see this page.
curl.exe -i http://localhost:8080/broken
HTTP/1.1 500 Internal Server Error
Something went wrong on the server.
curl.exe -i http://localhost:8080/created
HTTP/1.1 201 Created
This reply has the status 201 Created.
http.NotFound(w, r)mengirim 404 yang sama dengan router.http.Error(w, text, code)mengirim status dan teksnya sebagai teks biasa. Pakai ini untuk sebagian besar error.http.StatusForbiddendan kerabatnya adalah konstanta untuk angka-angka itu. Namanya mengatakan artinya, jadi pakailah sebagai ganti403.
Kedua fungsi itu tidak mengakhiri handlermu. Keduanya menulis
response, lalu kodemu berlanjut. Jadi tulis return
sesudahnya, kalau masih ada kode yang mengikuti. Bab berikutnya
menunjukkan apa yang terjadi kalau kamu lupa.
Menyajikan file dari folder
Kadang sebuah halaman butuh file apa adanya, seperti stylesheet. Go
bisa menyajikan seluruh folder dengan dua baris. Contoh ini butuh
foldernya, jadi dia berupa modul tersendiri, di
exercises/32/04-static-files/:
04-static-files/
go.mod
main.go
static/
notes.txt
style.css
Dua baris itu, di dalam main:
files := http.FileServer(http.Dir("static"))
http.Handle("GET /static/", http.StripPrefix("/static/", files))http.FileServer adalah handler siap pakai yang menjawab
dengan file dari sebuah folder. http.StripPrefix melepas
/static/ dari path lebih dulu, jadi
/static/notes.txt mencari notes.txt di dalam
folder itu. Karena handler ini adalah nilai dengan metode dan bukan
fungsimu sendiri, dia didaftarkan dengan http.Handle dan
bukan http.HandleFunc.
Nama foldernya relatif terhadap tempat program berjalan, jadi jalankan dari foldernya sendiri:
cd exercises/32/04-static-files
go run .
Untuk kembali ke go-work sesudahnya:
cd ../../..
Buka http://localhost:8080, dan judulnya biru karena
file itu. Halaman itu menautkan stylesheet-nya lewat path lokal. Semua
yang dibutuhkan halaman berasal dari servermu sendiri. Request untuk
file di luar folder itu tidak berhasil:
curl.exe -i http://localhost:8080/static/notes.txt
HTTP/1.1 200 OK
These notes are a file in the static folder.
curl.exe -i http://localhost:8080/main.go
HTTP/1.1 404 Not Found
404 page not found
File server menyajikan semua yang ada di foldernya, dan request untuk
/static/ mendaftar nama-nama file. Taruh hanya file publik
di sana. Bagian lain buku ini tidak membutuhkan file server.
PR
Starter PR ada di exercises/32/homework/. Masing-masing
bisa langsung dijalankan dan menjawab dengan satu baris pengganti.
Soal 1 — Tiga halaman
Buka exercises/32/homework/01-three-pages.go. Buat tiga
rute yang hanya menjawab GET: halaman depan di
/, halaman di /about, dan halaman di
/contact. Masing-masing menjawab dengan satu baris teks
biasa. Path lain harus memberikan 404, termasuk
/contact/extra.
Soal 2 — Halaman HTML
Buka exercises/32/homework/02-html-page.go. Jawab di
/ dengan halaman HTML yang punya judul, satu paragraf, dan
daftar tiga hal yang kamu sukai. Simpan HTML-nya dalam konstanta, dan
atur header Content-Type.
Soal 3 — Kode status
Buka exercises/32/homework/03-status-codes.go. Tambahkan
tiga rute:
GET /okmenjawab dengan status 200 danAll good.GET /gonemenjawab denganhttp.NotFound.GET /lockedmenjawab denganhttp.Error, teksLocked., dan status 403.
Periksa status masing-masing dengan curl.exe -i.
Tantangan — Situs kecil
Buka exercises/32/homework/04-small-site.go. Tulis
fungsi pembantu:
func writePage(w http.ResponseWriter, status int, title string, body string)Fungsi itu mengatur Content-Type, mengatur status, dan
menulis seluruh halaman HTML dengan judul dan body tersebut. Pakai dari
tiga handler: halaman depan, /about, dan satu halaman
buatanmu. Setiap halaman punya tautan ke dua halaman lainnya. Lalu buat
halaman 404-mu sendiri: pola "GET /" menangkap path GET apa
pun yang tidak cocok dengan rute lain, jadi handler di sana bisa
memanggil writePage dengan
http.StatusNotFound.
Stuck atau sudah selesai? Buka halaman solusi PR.