HTTP merupakan protokol yang banyak digunakan, baik pada aplikasi pada smartphone yang kita gunakan atau untuk menyajikan halaman website. Pada praktik ini kita akan membuat sebuah HTTP server yang dapat mengarahkan request dari klien ke beberapa fungsi golang, menyajikan file statis, dan merespon request dari fungsi yang kita buat.
Menggunakan paket net/http
Untuk membuat http server digolang, kita hanya perlu menggunakan net/http
seperti contoh kode dibawah:
package main
import (
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if len(port) == 0 {
port = ":8080"
}
log.Fatal(http.ListenAndServe(port, nil))
}
Pada contoh diatas, selain menggunakan package net/http
kita juga menggunakan package log
dan os
. Package tersebut digunakan hanya untuk memudahkan kita
untuk menampilkan log
error untuk mendebug aplikasi yang akan kita buat, dan menggunakan Environment Variabel yang telah kita set sebelumnya pada sistem os
.
Kedua package tersebut bisa dihilangkan jika kamu tidak ingin menggunakannya dan pada fungsi main cukup tulis http.ListenAndServe(":8080", nil)
.
Coba jalankan program (go run main.go
) dan buka alamat http://localhost:8080
untuk melihat hasilnya. Jika kamu ingin menentukan nomor port kamu sendiri kamu bisa
set Environment Variabel pada sistem contohnya:
export PORT=:80
# kemudian jalankan program
go run main.go
Dengan mensetting port 80
, maka kita bisa mengunjungi aplikasi yang akan kita buat di http://localhost
.
Untuk memastikan http server yang kita tulis bekerja dengan baik, kunjungi alamat tersebut pada browser. Saat ini http server kita hanya menampilkan
pesan 404 page not found
karena kita belum memiliki handler untuk merespon permintaan klien pada halaman tersebut. Kode 404
artinya resource yang diminta
tidak tersedia di server.
Membuat Fungsi Handle Request
Agar server dapat menaggapi permintaan dari klien, kita bisa menggunakan ServeMux
untuk membuat fungsi Handler
. Fungsi Handler
ini yang nantinya
digunakan server untuk menentukan konten seperti apa yang harus disajikan ke klien, ketika klien meminta atau mengunjungi url tertentu.
Pada contoh disini kita membuat dua handler ( halaman index /
dan halaman tentang /tentang
) yang menyajikan teks menggunakan fungsi handler yang kita buat.
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
mux := http.NewServeMux()
port := os.Getenv("PORT")
if len(port) == 0 {
port = ":8080"
}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Halo dari halaman index")
})
mux.HandleFunc("/tentang", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hanya demo http server")
})
log.Fatal(http.ListenAndServe(port, mux))
}
Sekarang aplikasi kita bisa menaggapi permintaan klien mengggunakan fungsi handler yang kita buat. Jika kita menggunjugi path /
dan /about
di browser maka server akan
menyajikan teks sesuai dengan yang kita tulis pada fungsi handler yang kita register pada path tersebut.
Pastikan exit program ketika mengubah source kode main.go
(CTRL+c
pada terminal), dan jalankan kembali perintah go run main.go
untuk melihat
hasil perubahan pada program.
Menggunakan URL Query parameter (GET Request)
Pada contoh sebelumnya kita telah membuat fungsi Handler untuk menanggapi permintaan klien pada path /
, dimana server hanya menyajikan teks
yang statis ketika klien mengunjungi path tersebut. Agar lebih terlihat dinamis, kita bisa menggunakan fitur query parameter sehingga server
bisa menampilkan teks yang berbeda sesuai query yang diminta klien.
Mari kita ubah fungsi handler yang kita buat pada contoh kode sebelumnya, dan menambahkan kode untuk menggunakan query parameter dari klien.
Pada contoh disini kita akan mengubah fungsi handler pada path /
seperti berikut.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
nama := r.FormValue("nama")
fmt.Fprintf(w, "Halo <b>%s</b> \nurl.Values : %v", nama, r.Form)
})
Untuk melihat hasilnya, coba buka browser dan kunjungi http://localhost/?nama=yaka
. yaka
adalah nilai query yang akan diproses oleh server,
kamu bisa mengantinya dengan nama kamu.
Pada kode yang kita tulis, server akan mengakses nilai tersebut pada http.Request
di field Form
. Pada contoh tersebut kita juga menggunakan fungsi
FormValue
untuk mengakses nilai tersebut dari field Form
menggunakan kunci tertentu. Pada contoh diatas kita ingin mengakses kunci nama
, kamu bisa
menambahkan kunci lain yang ingin diakses. Misal untuk mengakses kunci alamat
, dari query http://localhost/?nama=yaka&alamat=pesawaran
.
Menyajikan Static File
Website pada umumnya tidak hanya menampilkan teks sebagai respon pada kliennya, respon bisa berupa file (gambar, dokumen) atau halaman web.
Dengan package net/http
kita juga bisa melakukan hal tersebut menggunakan fungsi ServeFile
. Contoh berikut kita akan membuat fungsi handler baru pada
server yang kita buat untuk menyajikan halaman html
secara statis menggunakan fungsi ServeFile
.
mux.HandleFunc("/form", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "form.html")
})
Selanjutnya buat file form.html
jika belum dibuat dan simpan di folder yang sama dimana kita menyimpan source kode program kita.
Kamu bisa menuliskan semua tag html untuk membuat full format dokumen html di file tersebut, disini kita hanya akan menuliskan
bagian dari dokumen html di dalam tag <body>
.
<form action="" method="post">
<table>
<tr>
<th>Nama</th>
<th>Alamat</th>
<th>Aksi</th>
</tr>
<tr>
<td><input type="text" name="nama"></td>
<td><input type="text" name="alamat"></td>
<td><input type="submit" value="Submit"></td>
</tr>
</table>
</form>
Coba kunjugi path /form
di browser, maka server akan menyajikan file form.html.
Fungsi ServeFile
tidak hanya digunakan untuk menyajikan file, tapi juga bisa digunakan untuk menyajikan folder dan semua file didalamnya.
Misal http.ServeFile(w,r,"namafolder")
untuk menyajikan folder dan semua isi folder melalui server.
Memproses POST Request
Sebelumnya kita telah mencoba beberapa fitur untuk menyajikan permintaan GET
dari klien, tapi terkadang data query dari pengguna tidak harus selalu
dimasukkan melalui url parameter. Pada protokol http kita juga bisa menggunakan menerima payload dari pengguna, salah satunya yaitu dalam bentuk permintaan POST
.
Dimana klien mengirimkan data ke server pada path tertentu yang dapat menerima payload tersebut.
Pada contoh berikut, kita akan mengubah fungsi Handler pada path /form
untuk menanggapi permintaan GET
dan POST
dari klien.
mux.HandleFunc("/form", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
http.ServeFile(w, r, "form.html")
return
}
if r.Method == http.MethodPost {
// memanggil ParseForm() untuk memparse raw query
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Post data dari clien: %v", r.PostForm)
fmt.Fprintf(w, "\n<b>nama</b> : %s", r.FormValue("nama"))
fmt.Fprintf(w, "\n<b>alamat</b> : %s", r.FormValue("alamat"))
return
}
http.Error(w, "Metode yang digunakan tidak diimplementasikan", http.StatusNotAcceptable)
})
Pada contoh diatas kita akan menyajikan halaman statis form.html
jika klien mengakses halaman dengan GET
request dan memproses data yang di
kirim oleh klien jika metode yang digunakan adalah POST
request. Jika pengguna menggunakan metode selain kedua metode tersebut, maka server akan
memberikan respon error kode 406
Not Acceptable dengan kostum pesan seperti pada contoh diatas.
Pada protokol HTTP ada banyak sekali cara untuk melakukan request diantaranya yaitu CONNECT
, HEAD
, OPTIONS
, PUT
, PATCH
, TRACE
dan yang
paling umum ditemukan pada aplikasi berbasis website yaitu DELETE
, GET
dan POST
.
Menggunakan template/html
Pastinya akan sangat membingunkan jika kita menulis markup html
dan kode program golang dalam satu file source kode, untuk menghindari hal tersebut,
Golang memiliki package template/html
dimana kita bisa membuat file template dengan sintak khusus untuk menampilkan data.
Pada contoh disini kita akan memodifikasi fungsi pada handler /form
pada bagian POST
request method untuk menggunakan file data.html
sebagai template
untuk menampilkan data yang diinputkan dari form.html
.
mux.HandleFunc("/form", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
http.ServeFile(w, r, "form.html")
return
}
if r.Method == http.MethodGet {
tmpl := template.Must(template.ParseFiles("data.html"))
// memanggil ParseForm() untuk memparse raw query
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// fmt.Fprintf(w, "Post data dari clien: %v", r.PostForm)
// fmt.Fprintf(w, "\nnama : %s", r.FormValue("nama"))
// fmt.Fprintf(w, "\nalamat : %s", r.FormValue("alamat"))
data := map[string]interface{}{
"nama": r.FormValue("nama"),
"alamat": r.FormValue("alamat"),
}
tmpl.Execute(w, data)
return
}
http.Error(w, "Metode yang digunakan tidak diimplementasikan", http.StatusNotAcceptable)
})
Sama seperti cara sebelumnya Fprintf()
, bedanya disini kita menggunakan fungsi Execute()
untuk menampilkan data pada template yang telah di parse sebelumnya
pada variabel tmpl
Untuk kode templateya kita juga menggunakan sintaks utuk mengakses setiap data yang akan digunakan seperti berikut:
{{ with .}}
<table>
<tr>
<th>Nama</th>
<th>Alamat</th>
</tr>
<tr>
<td>{{.nama }}</td>
<td>{{.alamat }}</td>
</tr>
</table>
{{ end }}
Untuk melihat hasil dari perubahan yang dilakukan, restart server dengan menjalankan kembali go run main.go
dan isikan data pada form di browser.
Ketika form disubmit, data akan diproses dan dirender oleh server menggunakan template yang telah dibuat dan dikirim kembali ke klien untuk ditampilkan.