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))
}
Code Snippet 1: Contoh sederhana penggunaan package net/http

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))
}
Code Snippet 2: Registrasi Fungsi Handler menggunakan method HandleFunc pada ServeMux

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)
})
Code Snippet 3: Menggunakan URL Query parameter dari klien

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")
})
Code Snippet 4: Menyajikan dokumen file 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>
Code Snippet 5: Isi dokumen form.html

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)
})
Code Snippet 6: Menggunakan POST dan GET Request

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)
})
Code Snippet 7: Menggunakan template html

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 }}
Code Snippet 8: Template data.html untuk menampilkan data

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.

tampilan response pada browser
Figure 1: Contoh response GET dan POST Request