From aeacb662401ddeff3faff2d14edb16d8772d6e44 Mon Sep 17 00:00:00 2001 From: novalagung Date: Sat, 27 Apr 2024 21:36:30 +0700 Subject: [PATCH] feat: wording improvement, mainly on section B --- content/A-concurrency-pipeline.md | 8 +-- content/A-golang-generics.md | 2 +- content/A-pipeline-context-cancellation.md | 2 +- .../A-simplified-fan-in-fan-out-pipeline.md | 2 +- content/A-slice.md | 2 +- content/A-timer-ticker-scheduler.md | 2 +- content/B-ajax-json-payload.md | 2 +- content/B-form-value.md | 4 +- content/B-http-basic-auth.md | 60 ++++++++++--------- content/B-http-method-basic.md | 23 +++---- content/B-middleware-using-http-handler.md | 56 +++++++++-------- content/B-render-html-string.md | 10 ++-- content/B-render-specific-html-template.md | 15 +++-- content/B-routing-http-handlefunc.md | 19 +++--- content/B-routing-static-assets.md | 28 +++++---- ...erver-handler-http-request-cancellation.md | 38 ++++++------ content/B-simple-configuration.md | 51 ++++++++-------- content/B-template-actions-variables.md | 16 ++--- content/B-template-custom-functions.md | 14 ++--- content/B-template-functions.md | 6 +- content/B-template-render-html.md | 14 ++--- content/B-template-render-partial-html.md | 14 ++--- content/C-dockerize-golang.md | 4 +- content/C-echo-template-rendering.md | 2 +- content/C-golang-sso-saml-sp.md | 2 +- content/C-https-tls.md | 2 +- ...ert-1mil-csv-record-into-db-in-a-minute.md | 2 +- 27 files changed, 210 insertions(+), 190 deletions(-) diff --git a/content/A-concurrency-pipeline.md b/content/A-concurrency-pipeline.md index 056f5eac3..7d09766b1 100644 --- a/content/A-concurrency-pipeline.md +++ b/content/A-concurrency-pipeline.md @@ -8,7 +8,7 @@ Go memiliki beberapa API untuk keperluan konkurensi, dua diantaranya adalah *gor Definisi *pipeline* yang paling mudah versi penulis adalah **beberapa/banyak proses yang berjalan secara konkuren yang masing-masing proses merupakan bagian dari serangkaian tahapan proses yang berhubungan satu sama lain**. -Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang di mana database server yang perlu di-backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut: +Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang mana database server yang perlu di-backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut: 1. Kita perlu data *list* dari semua database yang harus di-backup, beserta alamat akses dan kredensial-nya. 2. Kita jalankan proses backup, bisa secara sekuensial (setelah `db1` selesai, lanjut `db2`, lanjut `db3`, dst), atau secara paralel (proses backup `db1`, `db2`, `db3`, dan lainnya dijalankan secara bersamaan). @@ -383,12 +383,12 @@ Fungsi `getSum()` menerima channel dan akan secara aktif memantau dan membaca da Nah, karena di sini kita punya 3 worker yang jelasnya menghasilkan 3 buah channel baru, kita perlu sebuah mekanisme untuk menggabung channel tersebut, agar nanti mudah untuk dikontrol ([SSoT](https://en.wikipedia.org/wiki/Single_source_of_truth)). Di sinilah peran fungsi `mergeChanFileInfo()`. -Fungsi `mergeChanFileInfo()` digunakan untuk *multiplexing* atau menggabung banyak channel ke satu channel saja, yang di mana channel ini juga akan **otomatis di-close ketika channel input (`chanFileContent`) adalah *closed***. Fungsi jenis seperti ini biasa disebut dengan **Fan-in function**. +Fungsi `mergeChanFileInfo()` digunakan untuk *multiplexing* atau menggabung banyak channel ke satu channel saja, yang mana channel ini juga akan **otomatis di-close ketika channel input (`chanFileContent`) adalah *closed***. Fungsi jenis seperti ini biasa disebut dengan **Fan-in function**. Jadi TL;DR nya: * Fungsi Fan-out digunakan untuk pembuatan worker, untuk distribusi job, yang proses distribusinya sendiri akan berhenti ketika channel inputan di-close. -* Fungsi Fan-in digunakan untuk *multiplexing* atau menggabung banyak worker ke satu channel saja, yang di mana channel baru ini juga otomatis di-close ketika channel input adalah closed. +* Fungsi Fan-in digunakan untuk *multiplexing* atau menggabung banyak worker ke satu channel saja, yang mana channel baru ini juga otomatis di-close ketika channel input adalah closed. Sekarang lanjut buat fungsi `getSum()`. @@ -408,7 +408,7 @@ func getSum(chanIn <-chan FileInfo) <-chan FileInfo { } ``` -Bisa dilihat, di situ channel inputan `chanIn` di-listen dan setiap ada penerimaan data (via channel tersebut) dilanjut ke proses kalkulasi md5 hash. Hasil hash-nya di tambahkan ke data `FileInfo` kemudian dikirim lagi ke channel `chanOut` yang di mana channel ini merupakan nilai balik fungsi `getSum()`. +Bisa dilihat, di situ channel inputan `chanIn` di-listen dan setiap ada penerimaan data (via channel tersebut) dilanjut ke proses kalkulasi md5 hash. Hasil hash-nya di tambahkan ke data `FileInfo` kemudian dikirim lagi ke channel `chanOut` yang mana channel ini merupakan nilai balik fungsi `getSum()`. Ketika `chanIn` closed, maka bisa diasumsikan semua data sudah dikirim. Jika memang iya dan data-data tersebut sudah di proses (pencarian md5hash-nya), maka channel `chanOut` juga di-close. diff --git a/content/A-golang-generics.md b/content/A-golang-generics.md index d78193665..07e85c9e3 100644 --- a/content/A-golang-generics.md +++ b/content/A-golang-generics.md @@ -63,7 +63,7 @@ Penulisan notasi fungsi dengan Generic kurang lebih sebagai berikut: func FuncName[dataType ](params) ``` -Pada kode di atas, tipe data `[]int` kita ganti menjadi tipe data `[]V`, yang di mana tipe `V` dideklarasikan dengan notasi `[V int]`. Tipe data `V` di situ artinya kompatibel atau *comparable* dengan tipe `int`. Bisa diambil kesimpulan kedua fungsi yang telah kita tulis adalah ekuivalen. +Pada kode di atas, tipe data `[]int` kita ganti menjadi tipe data `[]V`, yang mana tipe `V` dideklarasikan dengan notasi `[V int]`. Tipe data `V` di situ artinya kompatibel atau *comparable* dengan tipe `int`. Bisa diambil kesimpulan kedua fungsi yang telah kita tulis adalah ekuivalen. ```go func Sum(numbers []int) int { diff --git a/content/A-pipeline-context-cancellation.md b/content/A-pipeline-context-cancellation.md index c488ea9e1..a20895b7a 100644 --- a/content/A-pipeline-context-cancellation.md +++ b/content/A-pipeline-context-cancellation.md @@ -241,7 +241,7 @@ Cara pembuatan object context sendiri sebenarnya ada 3: 3. Menggunakan fungsi `context.With...`. Fungsi ini sebenarnya bukan digunakan untuk inisialisasi objek konteks baru, tapi digunakan untuk menambahkan informasi tertentu pada *copied* context yang disisipkan di parameter pertama pemanggilan fungsi. Ada 3 buah fungsi `context.With...` yang bisa digunakan, yaitu: - Fungsi `context.WithCancel(ctx) (ctx, cancel)`. Fungsi ini digunakan untuk menambahkan fasilitas *cancellable* pada context yang disisipkan sebagai parameter pertama pemanggilan fungsi. Lewat nilai balik kedua, yaitu `cancel` yang tipenya `context.CancelFunc`, kita bisa secara paksa meng-*cancel* context ini. - - Fungsi `context.WithDeadline(ctx, time.Time) (ctx, cancel)`. Fungsi ini juga menambahkan fitur *cancellable* pada context, tapi selain itu juga menambahkan informasi deadline yang di mana jika waktu sekarang sudah melebihi deadline yang sudah ditentukan maka context otomatis di-cancel secara paksa. + - Fungsi `context.WithDeadline(ctx, time.Time) (ctx, cancel)`. Fungsi ini juga menambahkan fitur *cancellable* pada context, tapi selain itu juga menambahkan informasi deadline yang mana jika waktu sekarang sudah melebihi deadline yang sudah ditentukan maka context otomatis di-cancel secara paksa. - Fungsi `context.WithTimeout(ctx, time.Duration) (ctx, cancel)`. Fungsi ini sama seperti `context.WithDeadline()`, bedanya pada parameter kedua argument bertipe durasi (bukan objek `time.Time`). Kesamaan dari ketiga fungsi `context.With...` adalah sama-sama menambahkan fasilitas *cancellable* yang bisa dieksekusi lewat nilai balik kedua fungsi tersebut (yang tipenya `context.CancelFunc`). diff --git a/content/A-simplified-fan-in-fan-out-pipeline.md b/content/A-simplified-fan-in-fan-out-pipeline.md index 896d12977..a8278f685 100644 --- a/content/A-simplified-fan-in-fan-out-pipeline.md +++ b/content/A-simplified-fan-in-fan-out-pipeline.md @@ -1,6 +1,6 @@ # A.63. Concurrency Pattern: Simplified Fan-out Fan-in Pipeline -Pada chapter sebelumnya, yaitu chapter [A.62. Concurrency Pattern: Pipeline](/A-concurrency-pipeline.html), kita telah mempelajari tentang pipeline pattern, yang di mana pattern tersebut merupakan rekomendasi dari tim Go dalam meng-*handle* jenis kasus sekarangkain proses yang berjalan secara konkuren. +Pada chapter sebelumnya, yaitu chapter [A.62. Concurrency Pattern: Pipeline](/A-concurrency-pipeline.html), kita telah mempelajari tentang pipeline pattern, yang mana pattern tersebut merupakan rekomendasi dari tim Go dalam meng-*handle* jenis kasus sekarangkain proses yang berjalan secara konkuren. > Penulis sangat anjurkan untuk mencoba mempelajari praktek chapter sebelumnya terlebih dahulu jika belum. Karena chapter kali ini ada hubungannya dengan chapter tersebut. diff --git a/content/A-slice.md b/content/A-slice.md index e0aa23f66..93ffee30d 100644 --- a/content/A-slice.md +++ b/content/A-slice.md @@ -151,7 +151,7 @@ fruits[x:y] **Slicing** yang dimulai dari indeks **0** hingga **y** akan mengembalikan elemen-elemen mulai indeks **0** hingga sebelum indeks **y**, dengan lebar kapasitas adalah sama dengan slice aslinya. -Sedangkan slicing yang dimulai dari indeks **x**, yang di mana nilai **x** adalah lebih dari **0**, membuat elemen ke-**x** slice yang diambil menjadi elemen ke-0 slice baru. Hal inilah yang membuat kapasitas slice berubah. +Sedangkan slicing yang dimulai dari indeks **x**, yang mana nilai **x** adalah lebih dari **0**, membuat elemen ke-**x** slice yang diambil menjadi elemen ke-0 slice baru. Hal inilah yang membuat kapasitas slice berubah. ## A.16.6. Fungsi `append()` diff --git a/content/A-timer-ticker-scheduler.md b/content/A-timer-ticker-scheduler.md index 699d096bf..92d4c4853 100644 --- a/content/A-timer-ticker-scheduler.md +++ b/content/A-timer-ticker-scheduler.md @@ -126,7 +126,7 @@ func main() { Pada contoh di atas bisa dilihat, selain ticker disiapkan juga variabel channel `done`. Variabel ini kita gunakan untuk mengontrol kapan ticker harus di stop. -Cara kerja program di atas: teknik `for` - `select` pada channel digunakan untuk mengecek penerimaan data dari channel `done` dan `ticker.C`. By default, channel `ticker.C` akan menerima kiriman data setiap `X` duration yang di mana pada kode di atas adalah 1 detik (lihat argumen inisialisasi objek ticker). +Cara kerja program di atas: teknik `for` - `select` pada channel digunakan untuk mengecek penerimaan data dari channel `done` dan `ticker.C`. By default, channel `ticker.C` akan menerima kiriman data setiap `X` duration yang mana pada kode di atas adalah 1 detik (lihat argumen inisialisasi objek ticker). Data yang dikirimkan via channel `ticker.C` adalah data date-time kapan event itu terjadi. Pada kode di atas, setiap ada kiriman data via channel tersebut kita tampilkan. diff --git a/content/B-ajax-json-payload.md b/content/B-ajax-json-payload.md index 5c42b2634..4beadd093 100644 --- a/content/B-ajax-json-payload.md +++ b/content/B-ajax-json-payload.md @@ -182,7 +182,7 @@ Isi payload didapatkan dengan cara men-decode body request (`r.Body`). Proses de - `json.Decoder` cocok digunakan untuk decode data JSON yang sumber datanya adalah stream `io.Reader`, contohnya seperti `r.Body`. - `json.Unmarshal()` cocok untuk proses decoding yang sumber datanya sudah tersimpan di variabel (bukan stream). -## B.14.5. Test +## B.14.5. Testing Jalankan program yang telah dibuat, test hasilnya di browser. diff --git a/content/B-form-value.md b/content/B-form-value.md index 5e849e814..a356a90c3 100644 --- a/content/B-form-value.md +++ b/content/B-form-value.md @@ -30,7 +30,7 @@ Pertama siapkan folder project baru dan sebuah file template view `view.html`. P {{end}} ``` -Aksi dari form di atas adalah `/process`, yang di mana url tersebut nantinya akan mengembalikan output berupa html hasil render template `result`. Silakan tulis template result berikut dalam `view.html` (jadi file view ini berisi 2 buah template). +Aksi dari form di atas adalah `/process`, yang mana url tersebut nantinya akan mengembalikan output berupa html hasil render template `result`. Silakan tulis template result berikut dalam `view.html` (jadi file view ini berisi 2 buah template). ```html {{define "result"}} @@ -123,7 +123,7 @@ Selain lewat method `FormValue()`, pengaksesan data juga bisa dilakukan dengan c Setelah data dari form sudah ditangkap oleh back-end, data ditampung dalam variabel `data` yang bertipe `map[string]string`. Variabel `data` tersebut kemudian disisipkan ke view, lewat statement `tmpl.Execute(w, data)`. -## B.12.3. Test +## B.12.3. Testing OK, sekarang coba jalankan program yang telah kita buat, dan cek hasilnya. diff --git a/content/B-http-basic-auth.md b/content/B-http-basic-auth.md index 41af9219b..5fed853e2 100644 --- a/content/B-http-basic-auth.md +++ b/content/B-http-basic-auth.md @@ -1,17 +1,19 @@ # B.18. HTTP Basic Authentication -HTTP Basic Auth adalah salah satu teknik otentikasi http request. Metode ini membutuhkan informasi username dan password untuk disisipkan dalam header request (dengan format tertentu), jadi cukup sederhana, tidak memerlukan cookies maupun session. Lebih jelasnya silakan baca [RFC-7617](https://tools.ietf.org/html/rfc7617). +HTTP Basic Auth adalah salah satu spesifikasi yang mengatur otentikasi pada HTTP request. Metode ini mewajibkan client request untuk menyertakan username dan password dalam header request. Dengan menerapkan basic auth maka kita tidak perlu menggunakan token untuk mendapatkan session. -Informasi username dan password tidak serta merta disisipkan dalam header, informasi tersebut harus di-encode terlebih dahulu ke dalam format yg sudah ditentukan sesuai spesifikasi, sebelum dimasukan ke header. +> Lebih jelasnya mengenai spesifikasi Basic Auth bisa di lihat di[RFC-7617](https://tools.ietf.org/html/rfc7617) -Berikut adalah contoh penulisan basic auth. +Informasi username dan password harus di-encode terlebih dahulu ke format yg sudah ditentukan sesuai spesifikasi, kemudian dijadikan value dari header `Authentication`. + +Berikut adalah contoh format penulisan basic auth: ```js // Request header Authorization: Basic c29tZXVzZXJuYW1lOnNvbWVwYXNzd29yZA== ``` -Informasi disisipkan dalam request header dengan key `Authorization`, dan value adalah `Basic` spasi hasil enkripsi dari data username dan password. Data username dan password digabung dengan separator tanda titik dua (`:`), lalu di-encode dalam format encoding Base 64. +Informasi disisipkan dalam request header dengan key `Authorization`, dan value adalah `Basic` diikut karakter spasi dan hasil encode terhadap data username dan password. Data username dan password digabung dengan separator tanda titik dua (`:`) lalu di-encode dalam format encoding Base64. ```js // Username password encryption @@ -19,14 +21,14 @@ base64encode("someusername:somepassword") // Hasilnya adalah c29tZXVzZXJuYW1lOnNvbWVwYXNzd29yZA== ``` -Golang menyediakan fungsi untuk meng-handle request basic auth dengan cukup mudah, jadi tidak perlu untuk memparsing header request terlebih dahulu untuk mendapatkan informasi username dan password. +Go menyediakan fasilitas untuk mengambil informasi basic auth dari suatu HTTP request dengan mudah, tanpa perlu untuk memparsing header request terlebih dahulu secara manual. ## B.18.1. Struktur Folder Proyek dan Endpoint -Ok, mari kita praktekan. Pada chapter ini kita akan membuat sebuah web service sederhana, isinya satu buah endpoint. Endpoint ini kita manfaatkan sebagai dua endpoint, dengan pembeda adalah informasi pada query string-nya. +Pada chapter ini kita akan membuat sebuah web service sederhana, isinya hanya satu buah endpoint. Endpoint ini didesain untuk bisa menerima query parameter atau tanpa query parameter. - - Endpoint `/student`, menampilkan semua data siswa. - - Endpoint `/student?id=s001`, menampilkan data siswa sesuai dengan id yang di minta. + - Endpoint `/student` menghasilkan response berisi semua data siswa + - Endpoint `/student?id=s001` menghasilkan response berisi data siswa sesuai dengan id yang di minta Data siswa sendiri merupakan slice object yang disimpan di variabel global. @@ -56,12 +58,16 @@ func main() { } ``` -Siapkan handler untuk rute `/student`. +Lalu siapkan handler untuk rute `/student`. ```go func ActionStudent(w http.ResponseWriter, r *http.Request) { - if !Auth(w, r) { return } - if !AllowOnlyGET(w, r) { return } + if !Auth(w, r) { + return + } + if !AllowOnlyGET(w, r) { + return + } if id := r.URL.Query().Get("id"); id != "" { OutputJSON(w, SelectStudent(id)) @@ -74,15 +80,15 @@ func ActionStudent(w http.ResponseWriter, r *http.Request) { Di dalam rute `/student` terdapat beberapa validasi. - - Validasi `!Auth(w, r)`; Nantinya akan kita buat fungsi `Auth()` untuk mengecek apakah request merupakan valid basic auth request atau tidak. - - Validasi `!AllowOnlyGET(w, r)`; Nantinya juga akan kita siapkan fungsi `AllowOnlyGET()`, gunanya untuk memastikan hanya request dengan method `GET` yang diperbolehkan masuk. + - Validasi `!Auth(w, r)`; Nantinya kita siapkan fungsi `Auth()` yang gunanya adalah untuk mengecek apakah request merupakan valid basic auth request atau tidak. + - Validasi `!AllowOnlyGET(w, r)`; Akan dibuat juga fungsi `AllowOnlyGET()`, tugasnya memastikan hanya request dengan method `GET` yang diperbolehkan masuk. -Setelah request lolos dari 2 validasi di atas, kita cek lagi apakah request ini memiliki parameter student id. +Setelah request lolos dari 2 validasi di atas, lanjut ke pengecekan berikutnya yaitu mendeteksi apakah request memiliki parameter student id. - - Ketika tidak ada parameter student id, maka endpoint ini mengembalikan semua data user yang ada, lewat pemanggilan fungsi `GetStudents()`. - - Sedangkan jika ada parameter student id, maka hanya user dengan id yg diinginkan yg dijadikan nilai balik, lewat fungsi `SelectStudent(id)`. + - Ketika tidak ada parameter student id, maka endpoint ini mengembalikan semua data user yang ada. Fungsi `GetStudents()` dieksekusi. + - Sedangkan jika ada parameter student id, maka hanya user dengan id yg diinginkan yg dijadikan nilai balik. Fungsi `SelectStudent(id)` dieksekusi. -Selanjutnya tambahkan satu fungsi lagi di main, `OutputJSON()`. Fungsi ini digunakan untuk mengkonversi data menjadi JSON string. +Selanjutnya tambahkan satu fungsi lagi di `main()` yaitu `OutputJSON()`. Fungsi ini digunakan konversi data ke bentuk JSON string. ```go func OutputJSON(w http.ResponseWriter, o interface{}) { @@ -97,11 +103,11 @@ func OutputJSON(w http.ResponseWriter, o interface{}) { } ``` -Konversi dari objek atau slice ke JSON string bisa dilakukan dengan memanfaatkan `json.Marshal`. Untuk lebih jelasnya silakan baca lagi chapter [A.53. JSON Data](/A-json.html). +Konversi dari objek atau slice ke JSON string dilakukan via `json.Marshal()`. Lebih jelasnya mengenai fungsi tersebut di bahas di chapter [A.53. JSON Data](/A-json.html). ## B.18.3. Data `Student` -Buka file `student.go`, siapkan struct `Student` dan variabel untuk menampung data yang bertipe `[]Student`. Data inilah yang dijadikan nilai balik di endpoint yang sudah dibuat. +Buka file `student.go`, siapkan struct `Student` dan variabel untuk menampung data yang bertipe `[]Student`. Data inilah yang nantinya dijadikan nilai balik endpoint `/student`. ```go package main @@ -115,7 +121,7 @@ type Student struct { } ``` -Buat fungsi `GetStudents()`, fungsi ini mengembalikan semua data student. Dan buat juga fungsi `SelectStudent(id)`, fungsi ini mengembalikan data student sesuai dengan id terpilih. +Buat fungsi `GetStudents()`, fungsi ini mengembalikan semua data student. Buat juga fungsi `SelectStudent(id)`, fungsi ini mengembalikan data student sesuai dengan id terpilih. ```go func GetStudents() []*Student { @@ -133,7 +139,7 @@ func SelectStudent(id string) *Student { } ``` -*Last but not least*, implementasikan fungsi `init()`, buat beberapa dummy data untuk ditampung pada variabel `students`. +*Last but not least*, implementasikan fungsi `init()` yang didalamnya berisi pembuatan beberapa dummy data untuk ditampung variabel `students`. > Fungsi `init()` adalah fungsi yang secara otomatis dipanggil ketika package tersebut di import atau di run. @@ -147,7 +153,7 @@ func init() { ## B.18.4. Fungsi `Auth()` dan `AllowOnlyGET()` -Selanjutnya, kita perlu menyiapkan beberapa fungsi yg digunakan pada `main.go`, yaitu `Auth()` dan `AllowOnlyGET()`. +Selanjutnya, ada dua fungsi lainnya yang perlu dipersiapkan yaitu `Auth()` dan `AllowOnlyGET()`. #### ◉ Fungsi `Auth()` @@ -186,11 +192,11 @@ Fungsi `r.BasicAuth()` mengembalikan 3 informasi: 2. Password 3. Nilai balik ke-3 ini adalah representasi valid tidak nya basic auth request yang sedang berlangsung -Jika basic auth request tidak valid, maka tampilkan pesan error sebagai nilai balik. Sedangkan jika basic auth adalah valid, maka dilanjutkan ke proses otentikasi, mengecek apakah username dan password yang dikirim cocok dengan username dan password yang ada di aplikasi kita. +Error dimunculkan ketika basic auth terdeteksi adalah tidak valid. Sedangkan jika ternyata valid, maka dilanjutkan ke proses otentikasi, mengecek apakah username dan password yang dikirim cocok dengan username dan password yang sudah di-*hardcode*. #### ◉ Fungsi `AllowOnlyGET()` -Fungsi ini bertugas untuk memastikan bahwa request yang diperbolehkan hanya yang ber-method `GET`. Selainnya, maka akan dianggap invalid request. +Fungsi ini bertugas memastikan bahwa request yang diperbolehkan hanya yang ber-method `GET`. Selainnya, maka dianggap invalid request. ```go func AllowOnlyGET(w http.ResponseWriter, r *http.Request) bool { @@ -205,17 +211,17 @@ func AllowOnlyGET(w http.ResponseWriter, r *http.Request) bool { ## B.18.5. Testing -Semuanya sudah siap, jalankan aplikasi. +Semuanya sudah siap, sekarang jalankan aplikasi. ```bash go run *.go ``` -Jangan menggunakan `go run main.go`, dikarenakan dalam package `main` terdapat beberapa file lain yang harus di-ikut-sertakan pada saat runtime. +Jangan menggunakan `go run main.go`, dikarenakan dalam package `main` terdapat beberapa file lain yang harus diikutsertakan pada saat runtime. ![Run the server](images/B_http_basic_auth_2_run_server.png) -Test web service kecil ini menggunakan command `curl`. +Test web service yang telah dibuat menggunakan command `curl`. ```bash $ curl -X GET --user batman:secret http://localhost:9000/student diff --git a/content/B-http-method-basic.md b/content/B-http-method-basic.md index e511f140c..3d930f39d 100644 --- a/content/B-http-method-basic.md +++ b/content/B-http-method-basic.md @@ -1,14 +1,14 @@ # B.11. HTTP Method: POST & GET -Setelah sebelumnya kita telah mempelajari banyak hal yang berhubungan dengan template view, kali ini topik yang terpilih adalah berbeda, yaitu mengenai penanganan http request di back end. +Sampai chapter ini, terhitung kita telah mempelajari banyak hal yang berhubungan dengan template view. Kali ini topik yang akan dibahas sedikit berbeda, yaitu mengenai penanganan HTTP request di back-end. -Sebuah route handler pada dasarnya bisa menerima segala jenis request, dalam artian: apapun jenis HTTP method-nya maka akan tetap masuk ke satu handler (seperti **POST**, **GET**, dan atau lainnya). Untuk memisah request berdasarkan method-nya, bisa menggunakan seleksi kondisi. +Sebuah route handler pada dasarnya bisa menerima segala jenis request, apapun jenis HTTP method-nya maka akan tetap masuk ke satu handler (seperti **POST**, **GET**, dan atau lainnya). Pengategorian request berdasarkan HTTP method bisa dilakukan menggunakan seleksi kondisi. -> Pada chapter lain kita akan belajar teknik routing yg lebih advance dengan bantuan routing library. +> Pada chapter lain kita akan belajar teknik routing yg lebih advance dengan bantuan *3rd party* routing library. ## B.11.1. Praktek -Silakan pelajari dan praktekkan kode berikut. +Mari coba praktekan. Disiapkan sebuah handler untuk rute `/` yang didalamnya ada pengecekan seleksi kondisi berdasarkan HTTP method. ```go package main @@ -33,21 +33,24 @@ func main() { } ``` -Struct `*http.Request` memiliki property bernama `Method` yang bisa digunakan untuk mengecek method daripada request yang sedang berjalan. +Struct `*http.Request` memiliki property bernama `Method`, isinya informasi HTTP method dari request. -Pada contoh di atas, request ke rute `/` dengan method POST akan menghasilkan output text `post`, sedangkan method GET menghasilkan output text `get`. +- Jika HTTP method adalah `POST`, maka text `post` dijadikan nilai response +- Jika HTTP method adalah `GET`, maka text `get` dijadikan nilai response -## B.11.2. Test +## B.11.2. Testing -Gunakan [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en), atau tools sejenisnya untuk mempermudah testing. Berikut adalah contoh request dengan method GET. +Gunakan [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en), atau tools sejenisnya untuk mempermudah testing. + +Berikut adalah contoh request dengan method GET. ![Request GET](images/B_http_method_basic_1_get.png) -Sedangkan di bawah ini adalah untuk method POST. +Dan di bawah ini adalah contoh request dengan method POST. ![Request POST](images/B_http_method_basic_2_post.png) -Jika method yang digunakan adalah selain POST dan GET, maka sesuai source code di atas, harusnya request akan menghasilkan response **400 Bad Request**. Di bawah ini adalah contoh request dengan method **PUT**. +Jika method yang digunakan adalah selain POST dan GET, maka web server menghasilkan response **400 Bad Request**. Di bawah ini adalah contoh request dengan method **PUT**. ![400 Bad Request](images/B_http_method_basic_3_bad_request.png) diff --git a/content/B-middleware-using-http-handler.md b/content/B-middleware-using-http-handler.md index 32825a580..1a82a6945 100644 --- a/content/B-middleware-using-http-handler.md +++ b/content/B-middleware-using-http-handler.md @@ -1,35 +1,41 @@ # B.19. Middleware `http.Handler` -Pada chapter ini, kita akan belajar penggunaan interface `http.Handler` untuk implementasi custom middleware. Kita akan menggunakan sample proyek pada chapter sebelumnya [B.18. HTTP Basic Auth](/B-http-basic-auth.html) sebagai dasar bahan pembahasan chapter ini. +Pada chapter ini, topik yang dibahas adalah penerapan interface `http.Handler` untuk implementasi custom middleware. Kita gunakan sample proyek pada chapter sebelumnya [B.18. HTTP Basic Auth](/B-http-basic-auth.html) sebagai dasar bahan pembahasan chapter ini. -> Apa itu middleware? Istilah middleware berbeda-beda di tiap bahasa/framework. NodeJS dan Rails ada istilah middleware. Pada pemrograman Java Enterprise, istilah filters digunakan. Pada C# istilahnya adalah delegate handlers. Definisi dari middleware sendiri versi penulis, sebuah blok kode yang dipanggil sebelum ataupun sesudah http request di proses. +> Apa itu middleware? +> +> Istilah middleware berbeda-beda di tiap bahasa/framework. Di NodeJS dan Rails ada istilah middleware. Pada pemrograman Java Enterprise, istilah filters digunakan. Pada C# middleware disebut dengan delegate handlers. Definisi sederhana middleware adalah sebuah blok kode yang dipanggil sebelum ataupun sesudah http request di proses. -Pada chapter sebelumnya, kalau dilihat, ada beberapa proses yang dijalankan dalam handler rute `/student`, yaitu pengecekan otentikasi dan pengecekan method. Misalnya terdapat rute lagi, maka dua validasi tersebut juga harus dipanggil lagi dalam handlernya. +Pada chapter sebelumnya, terdapat beberapa proses yang dijalankan dalam handler rute `/student`, yaitu pengecekan otentikasi dan pengecekan HTTP method. Misalnya terdapat rute lagi, maka dua validasi tersebut juga harus dipanggil lagi dalam handlernya. ```go func ActionStudent(w http.ResponseWriter, r *http.Request) { - if !Auth(w, r) { return } - if !AllowOnlyGET(w, r) { return } + if !Auth(w, r) { + return + } + if !AllowOnlyGET(w, r) { + return + } // ... } ``` -Jika ada banyak rute, apa yang harus kita lakukan? salah satu solusi yang bisa digunakan adalah dengan memanggil fungsi `Auth()` dan `AllowOnlyGet()` di semua handler rute yang ada. Namun jelasnya ini bukan best practice. Dan juga belum tentu di tiap rute hanya ada dua validasi ini, bisa saja ada lebih banyak proses, misalnya pengecekan csrf, authorization, dan lainnya. +Jika ada banyak rute, apa yang harus kita lakukan? salah satu solusi adalah dengan memanggil fungsi `Auth()` dan `AllowOnlyGet()` di setiap handler rute yang ada. Namun jelasnya ini bukan best practice karena mengharuskan penulisan kode yang berulang-ulang. Selain itu, bisa jadi ada jenis validasi lainnya yang harus diterapkan, misalnya misalnya pengecekan csrf, authorization, dan lainnya. Maka perlu ada desain penataan kode yang lebih efisien tanpa harus menuliskan validasi yang banyak tersebut berulang-ulang. -Solusi dari masalah tersebut adalah, mengkonversi fungsi-fungsi di atas menjadi middleware. +Solusi yang pas adalah dengan membuat middleware baru untuk keperluan validasi. ## B.19.1. Interface `http.Handler` -Interface `http.Handler` merupakan tipe data paling populer di Go untuk keperluan manajemen middleware. Struct yang mengimplementasikan interface ini diwajibkan memilik method dengan skema `ServeHTTP(ResponseWriter, *Request)`. +Interface `http.Handler` merupakan tipe data paling populer di Go untuk keperluan manajemen middleware. Struct yang mengimplementasikan interface ini diwajibkan untuk memilik method dengan skema `ServeHTTP(ResponseWriter, *Request)`. -Di Go sendiri objek utama untuk keperluan routing yaitu `mux` atau multiplexer, adalah mengimplementasikan interface `http.Handler` ini. +> Di Go, objek utama untuk keperluan routing web server adalah `mux` (kependekan dari multiplexer), dan `mux` ini mengimplementasikan interface `http.Handler`. -Dengan memanfaatkan interface ini, kita akan membuat beberapa middleware. Fungsi pengecekan otentikasi dan pengecekan method akan kita ubah menjadi middleware terpisah. +Kita akan buat beberapa middleware baru dengan memanfaatkan interface `http.Handler` untuk keperluan pengecekan otentikasi dan pengecekan HTTP method. ## B.19.2. Persiapan -OK, mari kita praktekan. Pertama duplikat folder project sebelumnya sebagai folder proyek baru. Lalu pada `main.go`, ubah isi fungsi `ActionStudent` dan `main`. +OK, mari masuk ke bagian *coding*. Pertama duplikat folder project sebelumnya sebagai folder proyek baru. Lalu pada `main.go`, ubah isi fungsi `ActionStudent()` dan `main()`. - Fungsi`ActionStudent()` @@ -66,13 +72,15 @@ OK, mari kita praktekan. Pertama duplikat folder project sebelumnya sebagai fold ``` -Perubahan pada kode `ActionStudent()` adalah, pengecekan basic auth dan pengecekan method dihapus. Selain itu di fungsi `main()` juga terdapat cukup banyak perubahan, yang detailnya akan kita bahas di bawah ini. +Perubahan pada kode `ActionStudent()` adalah penghapusan kode untuk pengecekan basic auth dan HTTP method. Selain itu, di fungsi `main()` juga terdapat cukup banyak perubahan, yang detailnya akan dijelaskan sebentar lagi. ## B.19.3. Mux / Multiplexer Di Go, mux (kependekan dari multiplexer) adalah router. Semua routing pasti dilakukan lewat objek mux. -Apa benar? routing `http.HandleFunc()` sepertinya tidak menggunakan mux? Begini, sebenarnya routing tersebut juga menggunakan mux. Go memiliki default objek mux yaitu `http.DefaultServeMux`. Routing yang langsung dilakukan dari fungsi `HandleFunc()` milik package `net/http` sebenarnya mengarah ke method default mux `http.DefaultServeMux.HandleFunc()`. Agar lebih jelas, silakan perhatikan dua kode berikut. +Apa benar? Routing `http.HandleFunc()` sepertinya tidak menggunakan mux? Begini, sebenarnya routing tersebut juga menggunakan mux. Go memiliki default objek mux yaitu `http.DefaultServeMux`. Routing yang langsung dilakukan dari fungsi `HandleFunc()` milik package `net/http` sebenarnya mengarah ke method default mux `http.DefaultServeMux.HandleFunc()`. + +Agar lebih jelas perbedaannya, silakan perhatikan dua kode berikut. ```go http.HandleFunc("/student", ActionStudent) @@ -83,13 +91,13 @@ mux := http.DefaultServeMux mux.HandleFunc("/student", ActionStudent) ``` -Dua kode di atas menghasilkan hasil yang sama persis. +Dua kode di atas melakukan prosees yang ekuivalen. -Mux sendiri adalah bentuk nyata struct yang mengimplementasikan interface `http.Handler`. Untuk lebih jelasnya silakan baca dokumentasi package net/http di https://golang.org/pkg/net/http/#Handle. +Mux sendiri adalah bentuk nyata struct yang mengimplementasikan interface `http.Handler`. Di kode setelah routing, bisa dilihat objek `mux` ditampung ke variabel baru bertipe `http.Handler`. Seperti ini adalah valid karena memang struct multiplexer memenuhi kriteria interface `http.Handler`, yaitu memiliki method `ServeHTTP()`. -Kembali ke pembahasan source code. Di kode setelah routing, bisa dilihat objek `mux` ditampung ke variabel baru bertipe `http.Handler`. Seperti ini adalah valid karena memang struct multiplexer memenuhi kriteria interface `http.Handler`, yaitu memiliki method `ServeHTTP()`. +> Untuk lebih jelasnya silakan baca dokumentasi package net/http di [https://golang.org/pkg/net/http/#Handle](https://golang.org/pkg/net/http/#Handle) -Lalu dari objek `handler` tersebut, ke-dua middleware dipanggil dengan parameter adalah objek `handler` itu sendiri dan nilai baliknya ditampung pada objek yang sama. +Lalu dari objek `handler` tersebut, ke-dua middleware dipanggil dengan argument parameter diisi objek `handler` itu sendiri, dan nilai baliknya ditampung pada objek yang sama. ```go var handler http.Handler = mux @@ -97,12 +105,12 @@ handler = MiddlewareAuth(handler) handler = MiddlewareAllowOnlyGet(handler) ``` -Fungsi `MiddlewareAuth()` dan `MiddlewareAllowOnlyGet()` adalah middleware yang akan kita buat setelah ini. Cara registrasi middleware yang paling populer adalah dengan memanggilnya secara sekuensial atau berurutan, seperti pada kode di atas. +Fungsi `MiddlewareAuth()` dan `MiddlewareAllowOnlyGet()` adalah middleware yang akan kita buat sebentar lagi. Cara registrasi middleware yang paling populer adalah dengan memanggilnya secara sekuensial atau berurutan, seperti pada kode di atas. - - `MiddlewareAuth()` bertugas untuk melakukan pengencekan credentials, basic auth. - - `MiddlewareAllowOnlyGet()` bertugas untuk melakukan pengecekan method. + - `MiddlewareAuth()` bertugas melakukan pengencekan credentials, basic auth. + - `MiddlewareAllowOnlyGet()` bertugas melakukan pengecekan method. -> Silakan lihat source code beberapa library middleware yang sudah terkenal seperti gorilla, gin-contrib, echo middleware, dan lainnya; Semua metode implementasi middleware-nya adalah sama, atau paling tidak mirip. Point plus nya, beberapa di antara library tersebut mudah diintegrasikan dan compatible satu sama lain. +> Silakan lihat source code beberapa library middleware yang sudah terkenal seperti gorilla, gin-contrib, echo middleware, dan lainnya; Semua metode implementasi middleware-nya adalah sama, atau minimal mirip. Point plus nya, beberapa di antara library tersebut mudah diintegrasikan dan *compatible* satu sama lain. Kedua middleware yang akan kita buat tersebut mengembalikan fungsi bertipe `http.Handler`. Eksekusi middleware sendiri terjadi pada saat ada http request masuk. @@ -138,9 +146,9 @@ func MiddlewareAuth(next http.Handler) http.Handler { } ``` -Idealnya fungsi middleware harus mengembalikan struct yang implements `http.Handler`. Beruntungnya, Go sudah menyiapkan fungsi ajaib untuk mempersingkat pembuatan struct-yang-implemenets-`http.Handler`. Fungsi tersebut adalah `http.HandlerFunc`, cukup bungkus callback `func(http.ResponseWriter,*http.Request)` sebagai tipe `http.HandlerFunc` dan semuanya beres. +Idealnya fungsi middleware harus mengembalikan struct yang implements `http.Handler`. Beruntungnya, Go sudah menyiapkan fungsi ajaib untuk mempersingkat pembuatan struct yang implement `http.Handler`, yaitu fungsi `http.HandlerFunc()`. Cukup bungkus callback `func(http.ResponseWriter,*http.Request)` sebagai tipe `http.HandlerFunc()` maka semuanya beres. -Isi dari `MiddlewareAuth()` sendiri adalah pengecekan basic auth, sama seperti pada chapter sebelumnya. +Isi dari `MiddlewareAuth()` sendiri adalah pengecekan basic auth (sama seperti pada chapter sebelumnya). Tak lupa, ubah juga `AllowOnlyGet()` menjadi `MiddlewareAllowOnlyGet()`. @@ -167,7 +175,7 @@ Lalu test menggunakan `curl`, hasilnya adalah sama dengan pada chapter sebelumny ![Consume API](images/B_http_basic_auth_3_test_api.png) -Dibanding metode pada chapter sebelumnya, dengan teknik ini kita bisa sangat mudah mengontrol lalu lintas routing aplikasi, karena semua rute pasti melewati middleware terlebih dahulu sebelum sampai ke tujuan. Cukup maksimalkan middleware tersebut tanpa menggangu fungsi callback masing-masing rute. +Dibanding metode pada chapter sebelumnya, dengan teknik ini kita lebih mudah mengontrol lalu lintas routing aplikasi, karena semua rute pasti melewati layer middleware terlebih dahulu sebelum sampai ke handler tujuan. Cukup maksimalkan saja penerapan middleware tanpa perlu menambahkan validasi di masing-masing handler. --- diff --git a/content/B-render-html-string.md b/content/B-render-html-string.md index 89230d4e5..5612c073a 100644 --- a/content/B-render-html-string.md +++ b/content/B-render-html-string.md @@ -1,6 +1,6 @@ # B.10. Template: Render HTML String -Output HTML yang muncul, selain bersumber dari template view, bisa juga bersumber dari sebuah string. Dengan menggunakan method `Parse()` milik `*template.Template` kita bisa menjadikan string html sebagai output. +Output HTML yang muncul, selain bersumber dari template view bisa juga bersumber dari sebuah string. Dengan menggunakan method `Parse()` milik `*template.Template` kita bisa menjadikan HTML string sebagai output di web. ## B.10.1. Praktek @@ -23,11 +23,11 @@ const view string = ` ` ``` -Konstanta bernama `view` bertipe `string` disiapkan, dengan isi adalah string html yang akan kita jadikan sebagai output nantinya. +Konstanta bernama `view` dengan tipe `string` disiapkan, isinya HTML string yang nanbtinya kita jadikan sebagai output pengaksesan endpoint. Kemudian buat fungsi `main()`, isinya adalah route handler `/index`. Dalam handler tersebut, string html `view` diparsing lalu dirender sebagai output. -Tambahkan juga rute `/`, yang isinya adalah me-redirect request secara paksa ke `/index` menggunakan fungsi `http.Redirect()`. +Tambahkan juga rute `/` yang isinya adalah me-redirect request secara paksa ke `/index` (via fungsi `http.Redirect()`). ```go func main() { @@ -47,9 +47,9 @@ func main() { } ``` -Pada kode di atas bisa dilihat, sebuah template bernama `main-template` disiapkan. Template tersebut diisi dengan hasil parsing string html `view` lewat method `Parse()`. +Pada kode di atas bisa dilihat sebuah template bernama `main-template` disiapkan. Template tersebut diisi dengan hasil parsing string html `view` lewat method `Parse()`. -## B.10.2. Test +## B.10.2. Testing Lakukan tes dan lihat hasilnya. diff --git a/content/B-render-specific-html-template.md b/content/B-render-specific-html-template.md index 6c71165a1..d24f618c4 100644 --- a/content/B-render-specific-html-template.md +++ b/content/B-render-specific-html-template.md @@ -1,6 +1,6 @@ # B.9. Template: Render Specific HTML Template -Pada chapter ini, kita akan belajar bagaimana cara untuk render template html tertentu. Sebuah file view bisa berisikan banyak template. Template mana yang ingin di-render bisa ditentukan. +Pada chapter ini kita akan mempelajari cara render template html tertentu untuk dijadikan output pengaksesan endpoint. Sebuah file view bisa berisikan banyak template. Template mana yang ingin di-render bisa ditentukan. ## B.9.1. Front End @@ -30,11 +30,14 @@ Siapkan folder project baru, buat file template bernama `view.html`, lalu isi de {{end}} ``` -Pada file view di atas, terlihat terdapat 2 template didefinisikan dalam 1 file, template `index` dan `test`. Rencananya template `index` akan ditampilkan ketika rute `/` diakses, dan template `test` ketika rute `/test` diakses. +Pada file view di atas, terlihat terdapat 2 template didefinisikan dalam 1 file, template `index` dan `test`. + +- Template `index` ditampilkan ketika rute `/` diakses +- Template `test` ditampilkan ketika rute `/test` diakses ## B.9.2. Back End -Selanjutnya siapkan back end program, buat file `main.go`, tulis kode berikut. +Selanjutnya siapkan kode di sisi back-end, buat file `main.go`, tulis kode berikut. ```go package main @@ -63,11 +66,11 @@ func main() { } ``` -Pada kode di atas bisa dilihat, terdapat 2 rute yang masing-masing memparsing file yang sama, tapi spesifik template yang dipilih untuk di-render berbeda. +Pada kode di atas bisa dilihat, terdapat 2 rute yang masing-masing mem-parsing file yang sama, tapi spesifik template yang dipilih untuk di-render berbeda. -Contoh di rute `/`, sebuah template dialokasikan dengan nama `index`, kemudian di-parsing-lah view bernama `view.html` menggunakan method `ParseFiles()`. Golang secara cerdas akan melakukan mencari dalam file view tersebut, apakah ada template yang namanya adalah `index` atau tidak. Jika ada akan ditampilkan. Hal ini juga berlaku pada rute `/test`, jika isi dari template bernama `test` akan ditampilkan tiap kali rute tersebut diakses. +Contoh di rute `/`, sebuah template dialokasikan dengan nama `index`, kemudian di-parsing-lah view bernama `view.html` menggunakan method `ParseFiles()`. Go secara cerdas melakukan pencarian dalam file view tersebut, apakah ada template yang namanya adalah `index` atau tidak. Jika ada maka ditampilkan. Hal ini juga berlaku pada rute `/test`, jika isi dari template bernama `test` ada maka ditampilkan. -## B.9.3. Test +## B.9.3. Testing Lakukan tes pada program yang telah kita buat, kurang lebih hasilnya seperti pada gambar berikut. diff --git a/content/B-routing-http-handlefunc.md b/content/B-routing-http-handlefunc.md index e62caf944..16d1de7cf 100644 --- a/content/B-routing-http-handlefunc.md +++ b/content/B-routing-http-handlefunc.md @@ -1,21 +1,18 @@ # B.2. Routing `http.HandleFunc` -Dalam Go, routing bisa dilakukan dengan beberapa cara, di antaranya: +Routing di Go bisa dilakukan dengan beberapa cara, di antaranya: 1. Dengan memanfaatkan fungsi `http.HandleFunc()` 2. Mengimplementasikan interface `http.Handler` pada suatu struct, untuk kemudian digunakan pada fungsi `http.Handle()` 3. Membuat multiplexer sendiri dengan memanfaatkan struct `http.ServeMux` - 4. Dan lainnya -Pada buku ini, semua cara tersebut akan dibahas, namun khusus pada chapter ini saja, hanya `http.HandleFunc()` yang kita pelajari. +Pada buku ini, semua cara tersebut akan dibahas, namun khusus di chapter ini hanya `http.HandleFunc()` yang kita pelajari. -> Metode routing cara pertama dan cara kedua memiliki kesamaan yaitu sama-sama menggunakan `DefaultServeMux` untuk pencocokan rute/endpoint yang diregistrasikan. Mengenai apa itu `DefaultServeMux` akan kita bahas lebih mendetail pada chapter lain. +> Metode routing cara pertama dan cara kedua memiliki kesamaan yaitu sama-sama menggunakan `DefaultServeMux` sebagai router. Mengenai apa itu `DefaultServeMux` akan kita bahas lebih mendetail pada chapter lain. ## B.2.1. Penggunaan `http.HandleFunc()` -Seperti yang sudah dijelaskan sekilas pada chapter sebelumnya, fungsi `http.HandleFunc()` digunakan untuk registrasi rute/endpoint dan handler-nya. - -Penggunaan fungsi ini cukup mudah, panggil saja fungsi lalu isi dua parameternya. +Seperti yang sudah dijelaskan sekilas pada chapter sebelumnya, fungsi `http.HandleFunc()` digunakan untuk registrasi rute/endpoint beserta handler-nya. Penggunaan fungsi ini cukup mudah, panggil saja fungsi lalu isi dua parameternya. 1. Parameter ke-1, adalah rute (atau endpoint). Sebagai contoh: `/`, `/index`, `/about`. 2. Parameter ke-2, berisikan handler untuk rute bersangkutan. Sebagai contoh handler untuk rute `/` bertugas untuk menampilkan output berupa html `

hello

`. @@ -29,7 +26,7 @@ import "fmt" import "net/http" ``` -Buat fungsi `main()`, di dalamnya siapkan sebuah closure `handlerIndex`, lalu gunakan closure tersebut sebagai handler dari dua rute baru yang diregistrasi, yaitu `/` dan `/index`. +Buat fungsi `main()`, di dalamnya siapkan sebuah closure `handlerIndex`, lalu gunakan closure tersebut sebagai handler dari dua rute baru yang sebentar lagi disiapkan, yaitu rute `/` dan `/index`. ```go func main() { @@ -42,7 +39,7 @@ func main() { } ``` -Selanjutnya, masih dalam fungsi `main()`, tambahkan rute baru `/data` dengan handler adalah anonymous function. +Selanjutnya, masih dalam fungsi `main()`, tambahkan rute `/data` dengan handler adalah anonymous function. ```go func main() { @@ -54,7 +51,7 @@ func main() { } ``` -Terakhir, jalankan server. +Terakhir, jalankan web server. ```go func main() { @@ -71,7 +68,7 @@ Tes dan lihat hasilnya. ![Rute `/data` mengembalikan data json](images/B_routing_http_handlefunc_1_routing.png) -Dalam routing, handler bisa berupa fungsi, closure, ataupun anonymous function; bebas, yang terpenting adalah skema fungsi-nya sesuai dengan `func (http.ResponseWriter, *http.Request)`. +Handler bisa berupa fungsi, closure, ataupun anonymous function, intinya bebas, yang terpenting adalah skema fungsi-nya harus sesuai dengan `func (http.ResponseWriter, *http.Request)`. --- diff --git a/content/B-routing-static-assets.md b/content/B-routing-static-assets.md index f2e1a045a..ceb7bea80 100644 --- a/content/B-routing-static-assets.md +++ b/content/B-routing-static-assets.md @@ -1,6 +1,8 @@ # B.3. Routing Static Assets -Pada bagian ini kita akan belajar bagaimana cara routing static assets atau static contents. Seperti file css, js, gambar, umumnya dikategorikan sebagai static assets. +Pada bagian ini kita akan mempelajari cara routing static assets / static contents. Static assets yang dimaksud adalah seperti file statis css, js, gambar, dan lainnya. + +Ok, mari belajar sambil praktek. ## B.3.1. Struktur Aplikasi @@ -8,7 +10,7 @@ Buat project baru, siapkan file dan folder dengan struktur sesuai dengan gambar ![Structure](images/B_routing_static_assets_1_structure.png) -Dalam folder `assets`, isi dengan file apapun, bisa gambar atau file js. Selanjutnya masuk ke bagian routing static assets. +Dalam folder `assets`, isi dengan file apapun bebas, bisa gambar atau file js. Selanjutnya masuk ke bagian routing static assets. ## B.3.2. Routing @@ -30,13 +32,13 @@ func main() { } ``` -Syarat yang dibutuhkan untuk routing static assets masih sama dengan routing handler, yaitu perlu didefiniskan rute-nya dan handler-nya. Hanya saja pembedanya, dalam routing static assets yang digunakan adalah `http.Handle()`, bukan `http.HandleFunc()`. +Syarat yang dibutuhkan untuk routing static assets masih sama dengan routing handler, yaitu perlu didefiniskan rute beserta handler-nya. Hanya saja pembedanya di sini adalah dalam routing static assets yang digunakan adalah `http.Handle()`, bukan `http.HandleFunc()`. 1. Rute terpilih adalah `/static/`, maka nantinya semua request yang di awali dengan `/static/` akan diarahkan ke sini. Registrasi rute menggunakan `http.Handle()` adalah berbeda dengan routing menggunakan `http.HandleFunc()`, lebih jelasnya akan ada sedikit penjelasan pada chapter lain. 2. Sedang untuk handler-nya bisa di-lihat, ada pada parameter ke-2 yang isinya statement `http.StripPrefix()`. Sebenarnya actual handler nya berada pada `http.FileServer()`. Fungsi `http.StripPrefix()` hanya digunakan untuk membungkus actual handler. -Fungsi `http.FileServer()` mengembalikan objek ber-tipe `http.Handler`. Fungsi ini berguna untuk men-serve semua http request, dengan konten yang didefinisikan pada parameter. Pada konteks ini yang di-maksud adalah `http.Dir("assets")`. Semua konten, entah file ataupun folder, yang ada di dalam folder `assets` akan di proses dalam handler. +Fungsi `http.FileServer()` mengembalikan objek ber-tipe `http.Handler`. Fungsi ini berguna untuk merespon http request dengan konten yang ada di dalam folder `assets` sesuai permintaan. Jalankan `main.go`, lalu test hasilnya di browser `http://localhost:9000/static/`. @@ -57,31 +59,31 @@ Jika dilihat pada struktur folder yang sudah di-buat, di dalam folder `assets` t * Request ke `/some/folder/test.png` mengarah path `./some/folder/test.png` relatif dari folder `assets` * ... dan seterusnya -> Fungsi `http.Dir()` berguna untuk adjustment path parameter. Separator dari path yang di-definisikan akan otomatis di-konversi ke path separator sesuai sistem operasi. +> Fungsi `http.Dir()` berguna untuk *adjustment path parameter*. Separator dari path yang di-definisikan otomatis di-konversi ke path separator sesuai sistem operasi. -Contoh selanjutnya, silakan perhatikan kode berikut. +Sekarang coba perhatikan kode berikut. ```go http.Handle("/static", http.FileServer(http.Dir("assets"))) ``` -Hasil dari routing: +Dengan skema routing di atas, maka: * Request ke `/static/site.css` mengarah ke `./static/site.css` relatif dari folder `assets` * Request ke `/static/script.js` mengarah ke `./static/script.js` relatif dari folder `assets` * Request ke `/static/some/folder/test.png` mengarah ke `./static/some/folder/test.png` relatif dari folder `assets` * ... dan seterusnya -Terlihat bahwa rute yang didaftarkan juga akan digabung dengan path destinasi file yang dicari, dan ini menjadikan path tidak valid. File `site.css` berada pada path `assets/site.css`, sedangkan dari routing di atas pencarian file mengarah ke path `assets/static/site.css`. Di sinilah kegunaan dari fungsi `http.StripPrefix()`. +Bisa dilihat bahwa rute yang didaftarkan juga akan digabung dengan path destinasi file yang dicari, dan ini menjadikan path tidak valid. File `site.css` berada pada path `assets/site.css`, sedangkan dari routing di atas pencarian file mengarah ke path `assets/static/site.css`. Di sinilah kegunaan dari fungsi `http.StripPrefix()`. -Fungsi `http.StripPrefix()` ini berguna untuk menghapus prefix dari endpoint yang di-request. Pada contoh paling atas, request ke url yang di awali dengan `/static/` hanya akan di ambil url setelahnya. +Fungsi `http.StripPrefix()` berguna untuk menghapus prefix dari endpoint yang diakses. Request ke URL yang di awali dengan `/static/` akan diambil informasi endpoint-nya tanpa prefix `/static/`. - * Request ke `/static/site.css` menjadi `/site.css` - * Request ke `/static/script.js` menjadi `/script.js` - * Request ke `/static/some/folder/test.png` menjadi `/some/folder/test.png` + * Request ke `/static/site.css` mengarah ke `site.css` + * Request ke `/static/script.js` mengarah ke `script.js` + * Request ke `/static/some/folder/test.png` mengarah ke `some/folder/test.png` * ... dan seterusnya -Routing static assets menjadi valid, karena file yang di-request akan cocok dengan path folder dari file yang di request. +Dengan penerapan `http.StripPrefix()` maka routing static assets menjadi valid, karena file yang di-request akan cocok dengan path folder yang telah dibuat. --- diff --git a/content/B-server-handler-http-request-cancellation.md b/content/B-server-handler-http-request-cancellation.md index 96300de53..83348a529 100644 --- a/content/B-server-handler-http-request-cancellation.md +++ b/content/B-server-handler-http-request-cancellation.md @@ -1,22 +1,20 @@ # B.23. Server Handler HTTP Request Cancellation -Dalam konteks web application, kadang kala sebuah http request butuh waktu cukup lama untuk selesai, bisa jadi karena kode yang kurang dioptimasi, atau prosesnya memang lama, atau mungkin faktor lainnya. Dari sisi client, biasanya ada handler untuk cancel request jika melebihi batas timeout yang sudah didefinisikan, dan ketika itu terjadi di client akan sangat mudah untuk antisipasinya. +Dalam konteks web application, kadang kala sebuah HTTP request butuh waktu cukup lama untuk selesai, bisa jadi karena kode yang kurang dioptimasi atau prosesnya memang lama, atau mungkin ada faktor lainnya. Dari sisi client, biasanya ada handler untuk cancel request ketika request melebihi batas timeout yang sudah ditentukan. -Berbeda dengan handler di back end-nya, by default request yang sudah di-cancel oleh client tidak terdeteksi (proses di back end akan tetap lanjut). Umumnya tidak ada masalah mengenai ini, tapi ada kalanya kita perlu men-treat cancelled request dengan baik untuk keperluan lain (logging, atau lainnya). +Berbeda dengan handler di back end-nya, by default request yang sudah di-cancel oleh client tidak mempengaruhi yang terjadi di back-end, proses di back end akan tetap lanjut hingga selesai. Umumnya hal ini bukan merupakan masalah, tapi untuk beberapa *case* ada baiknyakita perlu men-*treat* *cancelled request* dengan baik. Dan pada chapter ini kita akan belajar caranya. -Pada chapter ini kita akan belajar caranya. - -> Chapter ini fokus terhadap cancellation pada client http request. Untuk cancellation pada proses konkuren silakan merujuk ke [A.64. Concurrency Pattern: Context Cancellation Pipeline](/A-pipeline-context-cancellation.html). +> Chapter ini fokus terhadap cancellation pada client http request di sisi back-end. Untuk topik cancellation pada proses konkuren silakan pembahasannya ada di chapter [A.64. Concurrency Pattern: Context Cancellation Pipeline](/A-pipeline-context-cancellation.html). ## B.32.1. Praktek -Dari objek `*http.Request` bisa diambil objek context lewat method `.Context()`, dan dari context tersebut kita bisa mendeteksi apakah sebuah request di-cancel atau tidak oleh client. +Dari objek `*http.Request` informasi objek context bisa diakses lewat method `.Context()`, dan dari context tersebut kita bisa mendeteksi apakah sebuah request di-cancel atau tidak oleh client. -> Pada chapter ini kita tidak membahas secara rinci apa itu context. Silakan langsung merujuk ke chapter [D.2. Google API Search Dengan Timeout](/D-google-api-search.html) untuk lebih detailnya. +> Pada chapter ini kita tidak membahas secara rinci apa itu context karena sudah ada pembahasan terpisah mengenai topik tersebut di chapter [A.64. Concurrency Pattern: Context Cancellation Pipeline](/A-pipeline-context-cancellation.html). -Object context memiliki method `.Done()` yang nilai baliknya berupa channel. Dari channel tersebut kita bisa deteksi apakah request di-cancel atau tidak, caranya dengan cara mengecek apakah ada data yang terkirim lewat channel tersebut, jika ada maka lakukan pengecekan pada error message-nya, jika ada keterangan `"cancelled"` maka diasumsikan request tersebut dibatalkan. +Object context memiliki method `.Done()` yang nilai baliknya berupa channel. Dari channel tersebut kita bisa deteksi apakah request di-cancel atau tidak oleh client, jika ada data yang diterima via channel tersebut dan error yang didapat ada keterangan `"cancelled"` maka bisa diasumsikan request tersebut dibatalkan oleh client. -Mari kita praktekan langsung. Siapkan base kode sederhana berikut. +Mari kita praktekan langsung. Silakan mulai dengan menulis kode berikut. ```go package main @@ -39,9 +37,11 @@ func main() { } ``` -Di dalam `handleIndex()` disimulasikan sebuah proses membutuhkan waktu lama untuk selesai (kita gunakan `time.Sleep()` untuk ini). Umumnya kode akan dituliskan langsung dalam handler tersebut, tapi pada kasus ini tidak. Untuk bisa mendeteksi sebuah request di-cancel atau tidak, harus di-dispatch sebuah goroutine baru. +Di dalam `handleIndex()` disimulasikan sebuah proses membutuhkan waktu lama untuk selesai (kita gunakan `time.Sleep()` untuk ini). Umumnya kode dituliskan langsung dalam handler tersebut, tapi pada kasus ini tidak. Untuk bisa mendeteksi sebuah request di-cancel atau tidak kita akan manfaatkan goroutine baru. + +Dalam penerapannya ada dua pilihan opsi: -- Cara ke-1: bisa dengan menaruh proses utama di dalam gorutine tersebut, dan menaruh kode untuk deteksi di luar (di dalam handler-nya). +- Cara ke-1: Dengan menaruh proses utama di dalam gorutine tersebut, dan menaruh kode untuk deteksi di luar (di dalam handler-nya). - Cara ke-2: Atau sebaliknya. Menaruh proses utama di dalam handler, dan menempatkan deteksi cancelled request dalam goroutine baru. Pada contoh berikut, kita gunakan cara pertama. Tulis kode berikut dalam handler. @@ -70,14 +70,14 @@ case <-done: } ``` -Pada kode di atas terlihat, proses utama dibungkus dalam goroutine. Ketika selesai, sebuah data dikirimkan ke channel `done`. +Pada kode di atas terlihat, proses utama dibungkus dalam goroutine. Ketika selesai, maka back-end akan menerima data via channel `done`. -Lalu diluar, keyword `select` dipergunakan untuk deteksi pengiriman terhadap dua channel. +Keyword `select` di situ disiapkan untuk pendeteksian dua kondisi berikut: -- Channel `r.Context().Done()`, jika channel ini menerima data maka diasumsikan request selesai. Selanjutnya lakukan pengecekan pada objek error milik konteks untuk deteksi apakah selesai-nya request ini karena memang selesai, atau di-cancel oleh client, atau faktor lainnya. -- Channel `<-done`, jika channel ini menerima data, maka proses utama adalah selesai. +- Channel `r.Context().Done()`. Jika channel ini menerima data maka diasumsikan request selesai. Selanjutnya lakukan pengecekan pada objek error milik context untuk deteksi apakah selesai-nya request ini karena memang selesai, atau di-cancel oleh client, atau faktor lainnya. +- Channel `<-done`. Jika channel ini menerima data, maka proses utama adalah selesai. -Jalankan kode lalu test hasilnya. +Sekarang coba jalankan kode lalu test hasilnya. ```bash curl -X GET http://localhost:8080/ @@ -91,7 +91,9 @@ Pada gambar di atas terdapat dua request, yg pertama sukses dan yang kedua adala ## B.32.2. Handle Cancelled Request yang ada Payload-nya -Khusus untuk request dengan HTTP method yang mewajibkan untuk ada request body-nya (payload), maka channel `r.Context().Done()` tidak akan menerima data hingga terjadi proses read pada body payload. +Khusus untuk request dengan HTTP method yang memiliki request body (payload), maka channel `r.Context().Done()` tidak akan menerima data hingga terjadi proses read pada body payload. + +Silakan coba saja, misalnya dengan menambahkan kode berikut. ```go go func() { @@ -107,7 +109,7 @@ go func() { }() ``` -Jalankan ulang program kemudian test. +Hasilnya: ```go curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -d '{}' diff --git a/content/B-simple-configuration.md b/content/B-simple-configuration.md index 876b42d4f..699070a4f 100644 --- a/content/B-simple-configuration.md +++ b/content/B-simple-configuration.md @@ -1,12 +1,12 @@ # B.22. Simple Configuration -Dalam development, pasti banyak sekali variabel dan konstanta yang diperlukan. Mulai dari variabel yang dibutuhkan untuk start server seperti port, timeout, hingga variabel global dan variabel shared lainnya. +Dalam development, pastinya kita programmer akan berurusan dengan banyak sekali variabel dan konstanta untuk keperluan konfigurasi. Misalnya, variabel berisi informasi port web server, timeout, variabel global, dan lainnya. -Pada chapter ini, kita akan belajar cara membuat config file modular. +Pada chapter ini, kita akan belajar dasar pengelolahan variabel konfigurasi dengan memanfaatkan file JSON. ## B.22.1. Struktur Aplikasi -Pertama-tama, buat project baru, siapkan dengan struktur seperti gambar berikut. +Pertama-tama, buat project baru dengan struktur seperti gambar berikut. ![Structure](images/B_simple_configuration_1_structure.png) @@ -17,7 +17,7 @@ Folder `conf` berisi 2 file. ## B.22.2. File Konfigurasi JSON `config.json` -Semua konfigurasi perlu dituliskan dalam file ini. Desain struktur JSON nya untuk bisa mudah dipahami. Tulis data berikut di file tersebut. +Semua konfigurasi dituliskan dalam file ini. Desain struktur JSON-nya untuk bisa mudah dipahami, contoh: ```json { @@ -33,7 +33,7 @@ Semua konfigurasi perlu dituliskan dalam file ini. Desain struktur JSON nya untu } ``` -Ada 4 buah konfigurasi disiapkan. +Data JSON di atas berisi 4 buah data konfigurasi. 1. Property `server.port`. Port yang digunakan saat start web server. 2. Property `server.read_timeout`. Dijadikan sebagai timeout read. @@ -42,7 +42,7 @@ Ada 4 buah konfigurasi disiapkan. ## B.22.3. Pemrosesan Konfigurasi -Pada file `config.go`, nantinya kita akan buat sebuah fungsi, isinya mengembalikan objek cetakan struct representasi dari `config.json`. +Pada file `config.go` kita akan siapkan sebuah fungsi yang isinya mengembalikan objek cetakan struct didapat dari konten file `config.json`. Siapkan struct nya terlebih dahulu. @@ -71,14 +71,13 @@ type _Configuration struct { } ``` -Bisa dilihat pada kode di atas, struct bernama `_Configuration` dibuat. Struct ini berisikan banyak property yang strukturnya sama persis dengan isi file `config.json`. Dengan desain seperti ini, akan sangat memudahkan developer dalam pengaksesan konfigurasi. +Bisa dilihat pada kode di atas, struct bernama `_Configuration` dibuat. Struct ini berisikan banyak property yang strukturnya sama persis dengan isi file `config.json`. Dengan skema seperti itu akan cukup mempermudah developer dalam pengaksesan data konfigurasi. -Dari struct tersebut tercetak private objek bernama `shared`. Variabel inilah yang nantinya akan dikembalikan lewat fungsi yang akan kita buat. +Dari struct tersebut disiapkan objek bernama `shared`. Variabel ini berisi informasi konfigurasi hasil baca `config.json`, dan nantinya isinya bisa diakses via fungsi fungsi yang sebentar lagi akan dibuat. -Selanjutnya, isi `init()` dengan beberapa proses: membaca file json, lalu di decode ke object `shared`. - -Dengan menuliskan proses barusan ke fungsi `init()`, pada saat package `conf` ini di import ke package lain maka file `config.json` akan otomatis di parsing. Dan dengan menambahkan sedikit validasi, parsing hanya akan terjadi sekali di awal. +Selanjutnya, siapkan fungsi `init()` dengan isi operasi baca file `config.json` serta operasi decode data JSON dari isi file tersebut ke variabel `shared`. +Dengan adanya fungsi `init()` maka pada saat package `conf` ini di-import ke package lain otomatis file `config.json` dibaca dan di-parse untuk disimpan di variabel `shared`. Tambahkan juga validasi untuk memastikan kode hanya di-parse sekali saja. ```go func init() { @@ -107,7 +106,7 @@ func init() { } ``` -Lalu buat fungsi yang mengembalikan object `shared`. +Kemudian buat fungsi `Configuration()` yang isinya menjembatani pengaksesan object `shared`. ```go func Configuration() _Configuration { @@ -145,7 +144,7 @@ func (c CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { Bisa dilihat dalam method `ServeHTTP()` di atas, ada pengecekan salah satu konfigurasi, yaitu `Log.Verbose`. Cara pengaksesannya cukup mudah, yaitu lewat fungsi `Configuration()` milik package `conf` yang telah di-import. -OK, kembali lagi ke contoh, dari mux di atas, buat object baru bernama `router`, lalu lakukan registrasi beberapa rute. +OK, kembali lagi ke contoh, dari mux di atas dibuatkan object baru bernama `router`, lalu beberapa rute didaftarkan ke object mux tersebut. ```go func main() { @@ -161,8 +160,7 @@ func main() { } ``` -Selanjutnya, kita akan start web server untuk serve mux di atas. Masih di dalam `main.go`, tambahkan kode berikut. - +Selanjutnya, siapkan kode untuk start web server. Tulis kode berikut di dalam fungsi `main()` tepat setelah kode deklarasi route handler. ```go server := new(http.Server) @@ -181,18 +179,18 @@ if err != nil { } ``` -Objek baru bernama `server` telah dibuat dari struct `http.Server`. Untuk start server cukup panggil method `ListenAndServe()` milik objek tersebut. +Di atas, ada objek baru dibuat dari struct `http.Server`, yaitu `server`. Untuk start server, panggil method `ListenAndServe()` milik objek tersebut. Dengan memanfaatkan struct ini, kita bisa meng-custom beberapa konfigurasi default pada Go web server. Di antaranya seperti `ReadTimeout` dan `WriteTimeout`. -Pada kode di atas bisa kita lihat, ada 4 buah properti milik `server` di-isi. +Bisa dilihat di contoh ada 4 buah properti milik `server` yang diisi nilainya dengan data konfigurasi. - `server.Handler`. Properti ini wajib di isi dengan custom mux yang dibuat. - `server.ReadTimeout`. Adalah timeout ketika memproses sebuah request. Kita isi dengan nilai dari configurasi. - `server.WriteTimeout`. Adalah timeout ketika memproses response. - `server.Addr`. Port yang digunakan web server pada saat start. -Terakhir jalankan aplikasi, akses dua buah endpoint yang sudah dibuat, lalu coba cek di console. +Ok. Sekarang jalankan aplikasi, akses dua buah endpoint yang sudah dibuat, kemudian cek di console. ![Structure](images/B_simple_configuration_2_log.png) @@ -200,25 +198,25 @@ Coba ubah konfigurasi pada `config.json` nilai `log.verbose` menjadi `false`. La ## B.22.5. Kekurangan Konfigurasi File -Ok, kita telah selesai belajar tentang cara membuat file konfigurasi yang mudah dibaca dan praktis. Namun penerapan kontrol konfigurasi dengan metode ini kurang dianjurkan karena beberapa hal: +Ok, kita telah selesai belajar tentang cara membuat file konfigurasi yang terpusat dan mudah dibaca. Metode konfigurasi seperti ini umum digunakan, tapi dalam penerapannya memiliki beberapa *cons* yang mungkin akan mulai terasa ketika aplikasi arsitektur aplikasi berkembang dan arsitektur sistemnya menjadi kompleks. *Cons* yang dimaksud diantaranya adalah: #### ◉ Tidak mendukung komentar -Komentar sangat penting karena untuk aplikasi besar yang konfigurasi item-nya sangat banyak - akan susah untuk dipahami. Sebenarnya perihal ini bisa di-*resolve* menggunakan jenis konfigurasi lain seperti `YAML`, `.env`, atau lainnya. +Komentar sangat penting karena untuk aplikasi besar yang konfigurasi item-nya sangat banyak, konfigurasi seperti pada contoh ini akan cukup susah untuk dikelola. Sebenarnya masalah ini bisa diselesaikan dengan mudah dengan cara mengadopsi file format lainnya, misalnya `YAML`, `.env`, atau lainnya. #### ◉ Nilai konfigurasi harus diketahui di awal -Kita harus tau semua value tiap-tiap konfigurasi terlebih dahulu, dan dituliskan ke file, sebelum aplikasi di-up. Dari sini akan sangat susah jika misal ada beberapa konfigurasi yang kita tidak tau nilainya tapi tau cara pengambilannya. +Kita harus tau semua value tiap-tiap konfigurasi terlebih dahulu sebelum dituliskan ke file, dan sebelum aplikasi di-up. Dari sini akan sangat susah jika misal ada beberapa konfigurasi yang kita tidak tau nilainya tapi tau cara pengambilannya. -Contohnya pada beberapa kasus, seperti di AWS, database server yang di-setup secara automated akan meng-generate connection string yang host-nya bisa berganti-ganti tiap start-up, dan tidak hanya itu, bisa saja username, password dan lainnya juga tidak statis. +Contohnya seperti ini, di beberapa kasus, misalnya di AWS, database server yang di-setup secara automated akan meng-generate connection string yang host-nya bisa berganti-ganti tiap start-up, dan tidak hanya itu saja, bisa saja username, password dan lainnya juga tidak statis. -Dengan ini akan sangat susah jika kita harus cari terlebih dahulu value konfigurasi tersebut untuk kemudian dituliskan ke file. Memakan waktu dan kurang baik dari banyak sisi. +Dengan ini akan cukup merepotkan jika kita harus cari terlebih dahulu value konfigurasi tersebut untuk kemudian dituliskan ke file secara manual. #### ◉ Tidak terpusat -Dalam pengembangan aplikasi, banyak konfigurasi yang nilai-nya akan didapat lewat jalan lain, seperti *environment variables* atau *command arguments*. +Dalam pengembangan aplikasi, banyak konfigurasi yang nilai-nya akan didapat lewat jalan lain, seperti *environment variables* atau *command arguments*. Menyimpan konfigurasi file itu sudah cukup bagus, cuman untuk *case* dimana terdapat banyak sekali services, agak merepotkan pengelolahannya. -Akan lebih mudah jika hanya ada satu sumber konfigurasi saja untuk dijadikan acuan. +Ketika ada perubahan konfigurasi, semua services harus direstart. #### ◉ Statis (tidak dinamis) @@ -228,8 +226,7 @@ Hal tersebut memiliki beberapa konsekuensi, untuk aplikasi yang di-manage secara #### ◉ Solusi -Kita akan membahas solusi dari beberapa masalah di atas pada chapter terpisah, yaitu [Best Practice Configuration Menggunakan Environment Variable -](/C-best-practice-configuration-env-var) +Kita akan membahas solusi dari beberapa masalah di atas (tidak semuanya) pada chapter terpisah, yaitu [C.11. Best Practice Configuration Menggunakan Environment Variable](/C-best-practice-configuration-env-var.html) --- diff --git a/content/B-template-actions-variables.md b/content/B-template-actions-variables.md index 7e110dad1..042cff34d 100644 --- a/content/B-template-actions-variables.md +++ b/content/B-template-actions-variables.md @@ -1,13 +1,13 @@ # B.6. Template: Actions & Variables -[**Actions**](https://golang.org/pkg/text/template/#hdr-Actions) adalah *predefined* keyword yang sudah disediakan Go, biasa dimanfaatkan dalam pembuatan template. +[**Actions**](https://golang.org/pkg/text/template/#hdr-Actions) merupakan *predefined* keyword yang disediakan oleh Go. Actions biasa dimanfaatkan dalam pembuatan template. Sebenarnya pada dua chapter sebelumnya, secara tidak sadar kita telah menggunakan beberapa jenis actions, di antaranya: - Penggunaan **pipeline output**. Nilai yang diapit tanda \{\{ \}\}, yang nantinya akan dimunculkan di layar sebagai output, contohnya: \{\{"hello world"\}\}. - Include template lain menggunakan keyword `template`, contohnya: \{\{template "name"\}\}. -Pada chapter ini, kita akan belajar lebih banyak lagi tentang actions lain yang disediakan Go, juga cara pembuatan dan pemanfaatan variabel pada template view. +Pada chapter ini, kita akan belajar lebih banyak lagi tentang actions lain, juga cara pembuatan dan pemanfaatan variabel pada template view. ## B.6.1. Persiapan @@ -33,7 +33,7 @@ type Person struct { } ``` -Pada kode di atas, dua buah struct disiapkan, `Info` dan `Person` (yang di mana struct `Info` di-embed ke dalam struct `Person`). Kedua struct tersebut nantinya akan digunakan untuk pembuatan objek, yang kemudian object tersebut disisipkan ke dalam view. +Pada kode di atas, dua buah struct disiapkan, `Info` dan `Person` (yang mana struct `Info` di-embed ke dalam struct `Person`). Kedua struct tersebut nantinya akan digunakan untuk pembuatan objek untuk kemudian disisipkan ke dalam view. Selanjutnya, siapkan fungsi `main()`, dengan di dalamnya berisikan 1 buah route handler `/`, dan juga kode untuk menjalankan server pada port `9000`. @@ -58,9 +58,9 @@ func main() { } ``` -Pada route handler `/` di atas, variabel objek `person` dibuat, lalu disisipkan sebagai data pada view `view.html` yang sebelumya sudah diparsing. +Pada route handler `/` di atas, variabel objek `person` dibuat, kemudian disisipkan sebagai data pada view `view.html`. -Perlu diketahui, ketika data yang disisipkan ke view berbentuk `map`, maka `key` (yang nantinya akan menjadi nama variabel) boleh dituliskan dalam huruf kecil. Sedangkan jika berupa variabel objek `struct`, maka property harus dituliskan public (huruf pertama kapital). +Perlu diketahui, ketika data yang disisipkan ke view bertipe `map`, maka `key` (yang nantinya akan menjadi nama variabel) boleh dituliskan dalam huruf kecil. Sedangkan jika berupa variabel objek `struct`, maka property harus dituliskan public (huruf pertama kapital). > Data yang disisipkan ke view, jika tipe nya adalah struct, maka hanya properties ber-modifier public (ditandai dengan huruf kapital di awal nama property) yang bisa diakses dari view. @@ -82,7 +82,7 @@ Selanjutnya silakan ikuti step-step berikut. ## B.6.2. Pipeline Output & Komentar -Actions pertama yang akan kita coba terapkan adalah pipeline output, menampilkan output ke layar. Caranya cukup mudah, cukup dengan menuliskan apa yang ingin ditampilkan di layar dengan diapit tanda `{{ }}` (bisa berupa variabel yang dilempar dari back end, bisa juga literal string). +Actions pertama yang akan kita coba terapkan adalah pipeline output, menampilkan output ke layar. Caranya cukup mudah, dengan menuliskan apa yang ingin ditampilkan di layar dengan diapit tanda `{{ }}` (bisa berupa variabel yang dilempar dari back end, bisa juga literal string). Tulis kode berikut di dalam tag `
` pada `view.html`. @@ -135,7 +135,7 @@ Actions `range` digunakan untuk melakukan perulangan pada template view. Keyword ``` -Penulisannya cukup unik, keyword `range` dituliskan terlebih dahulu, diikuti variabel penampung index dan elemen. Jika yang dibutuhkan hanya elemen saja, bisa cukup gunakan `{{range $elem := .Hobbies}}`. Semua kode setelah baris deklarasi hingga penutup `{{end}}`, akan diulang sesuai jumlah elemen/item-nya. +Penulisannya cukup unik, keyword `range` dituliskan terlebih dahulu, diikuti variabel penampung index dan elemen. Jika yang dibutuhkan hanya elemen saja, maka gunakan `{{range $elem := .Hobbies}}`. Semua kode setelah baris deklarasi hingga penutup `{{end}}`, akan diulang sesuai jumlah elemen/item-nya. ![Perulangan](images/B_template_actions_variables_3_loop.png) @@ -175,7 +175,7 @@ Lalu bagaimana cara pengaksesan method yang membutuhkan parameter, jika tanda ku ## B.6.6. Penggunaan Keyword `with` Untuk Mengganti Scope Variabel Pada Suatu Blok -Secara default **current scope** di template view adalah data yang dilempar back end. Scope current objek bisa diganti dengan menggunakan keyword `with`, sehingga nantinya untuk mengakses sub-property variabel objek (seperti `.Info.Affiliation`), bisa tidak dilakukan dari objek terluar. +Default-nya, **current scope** di template view adalah data yang dilempar back end. Scope current objek bisa diganti dengan menggunakan keyword `with`, sehingga nantinya untuk mengakses sub-property variabel objek (seperti `.Info.Affiliation`), bisa tidak dilakukan dari objek terluar. > Current scope yg dimaksud di sini adalah seperti object `this` ibarat bahasa pemrograman lain. diff --git a/content/B-template-custom-functions.md b/content/B-template-custom-functions.md index 313853137..cf7281ef4 100644 --- a/content/B-template-custom-functions.md +++ b/content/B-template-custom-functions.md @@ -1,6 +1,6 @@ # B.8. Template: Custom Functions -Pada chapter sebelumnya kita telah mengenal beberapa *predefined* function yang disediakan oleh Go. Kali ini kita akan belajar tentang fungsi custom, bagaimana cara membuat dan menggunakannya dalam template. +Pada chapter sebelumnya kita telah berkenalan dengan beberapa *predefined* function yang disediakan oleh Go. Kali ini kita akan belajar tentang fungsi custom, bagaimana cara pembuatan dan penggunaannya dalam template. ## B.8.1. Front End @@ -20,14 +20,14 @@ Pertama, siapkan project baru. Buat file template `view.html`, lalu isi dengan k ``` -Ada 2 hal yang perlu diperhatikan dari kode di atas. Pertama, terdapat dua buah fungsi yang dipanggil beberapa kali. +Ada 2 hal yang perlu diperhatikan dari kode di atas. Terdapat dua buah fungsi yang dipanggil beberapa kali. 1. Fungsi `unescape()`, digunakan untuk menampilkan string tanpa di-escape 2. Fungsi `avg()`, digunakan untuk mencari rata-rata dari angka-angka yang disisipkan sebagai parameter Kedua fungsi tersebut adalah fungsi kustom yang akan kita buat. -Hal ke-2, terdapat 1 baris statement yang penulisannya agak unik, yaitu `{{"" | unescape}}`. Statement tersebut maknanya adalah string `""` digunakan sebagai parameter dalam pemanggilan fungsi `unescape`. Tanda pipe atau `|` adalah penanda bahwa parameter dituliskan terlebih dahulu sebelum nama fungsi nya. +Di contoh terdapat 1 baris statement yang penulisannya agak unik, yaitu `{{"" | unescape}}`. Statement tersebut maknanya adalah string `""` digunakan sebagai parameter dalam pemanggilan fungsi `unescape`. Tanda pipe atau `|` adalah penanda bahwa parameter dituliskan terlebih dahulu sebelum nama fungsi-nya. ## B.8.2. Back End @@ -62,7 +62,7 @@ var funcMap = template.FuncMap{ Dalam `funcMap` di atas, dua buah fungsi disiapkan, `unescape()` dan `avg()`. Nantinya fungsi ini kita gunakan di view. -Setelah itu, siapkan fungsi `main()` dengan isi route handler untuk `/`. Di dalam handler ini, `view.html` diparsing, kemudian disisipkan fungsi yang telah dibuat di atas ke dalamnya. +Setelah itu, siapkan fungsi `main()` dengan isi route handler untuk `/`. Di dalam handler ini, `view.html` diparsing, kemudian fungsi yang telah dibuat di atas disisipkan ke dalam view. ```go func main() { @@ -86,7 +86,7 @@ Berikut merupakan penjelasan step-by-step mengenai kode panjang untuk parsing da 2. Fungsi custom yang telah kita buat, diregistrasikan agar dikenali oleh template tersebut. Bisa dilihat pada pemanggilan method `Funcs()`. 3. Setelah itu, lewat method `ParseFiles()`, view `view.html` di-parsing. Akan dicari dalam file tersebut apakah ada template yang didefinisikan dengan nama `view.html`. Karena di dalam template view tidak ada deklarasi template sama sekali (\{\{template "namatemplate"\}\}), maka akan dicari view yang namanya adalah `view.html`. Keseluruhan isi `view.html` akan dianggap sebagai sebuah template dengan nama template adalah nama file itu sendiri. -## B.8.3. Test +## B.8.3. Testing Tes hasilnya lewat browser. @@ -96,9 +96,9 @@ Tes hasilnya lewat browser. Pada kode di atas, pemanggilan `template.New()` menghasilkan objek bertipe `*template.Template`. -Pada chapter [B.5. Template: Render Partial HTML Template](/B-template-render-partial-html.html) kita telah belajar mengenai fungsi `template.ParseFiles()`, yang fungsi tersebut juga mengembalikan objek bertipe `*template.Template`. +Pada chapter [B.5. Template: Render Partial HTML Template](/B-template-render-partial-html.html) kita telah belajar mengenai fungsi `template.ParseFiles()` yang fungsi tersebut juga mengembalikan objek bertipe `*template.Template`. -Pada kode di atas, method `ParseFiles()` yang dipanggil bukanlah fungsi `template.ParseFiles()` yang kita telah pelajari sebelumnya. Meskipun namanya sama, kedua fungsi/method ini berbeda. +Di contoh di chapter ini, method `ParseFiles()` yang dipanggil bukanlah fungsi `template.ParseFiles()` yang kita telah pelajari sebelumnya. Meskipun namanya sama, kedua fungsi/method ini berbeda. - Fungsi `template.ParseFiles()`, adalah milik package `template`. Fungsi ini digunakan untuk mem-parsing semua view yang disisipkan sebagai parameter. - Method `ParseFiles()`, milik `*template.Template`, digunakan untuk memparsing semua view yang disisipkan sebagai parameter, lalu diambil hanya bagian yang nama template-nya adalah sama dengan nama template yang sudah di-alokasikan menggunakan `template.New()`. Jika template yang dicari tidak ada, maka akan mencari yang nama file-nya sama dengan nama template yang sudah ter-alokasi. diff --git a/content/B-template-functions.md b/content/B-template-functions.md index a252f8b3e..342a7b721 100644 --- a/content/B-template-functions.md +++ b/content/B-template-functions.md @@ -1,6 +1,8 @@ # B.7. Template: Functions -Go menyediakan beberapa *predefiend* function yang bisa digunakan dalam file template. Pada chapter ini kita akan membahas beberapa di antaranya beserta cara penggunaannya. Cara pemanggilan fungsi atau method sebuah objek pada file template sedikit berbeda dibanding seperti pada chapter sebelumnya. +Go menyediakan beberapa *predefiend* function yang bisa digunakan langsung dalam file template. Pada chapter ini kita akan membahas beberapa di antaranya beserta cara penggunaannya. + +Cara pemanggilan fungsi atau method sebuah objek pada file template sedikit berbeda dibanding dengan yang telah dicontohkan pada chapter sebelumnya. ## B.7.1. Persiapan @@ -160,7 +162,7 @@ Go juga menyediakan beberapa fungsi string yang bisa dimanfaatkan, yaitu: - `printf` (merupakan alias dari `fmt.Sprintf`) - `println` (merupakan alias dari `fmt.Sprintln`) -Cara penggunannya juga masih sama. +Cara penggunannya juga masih sama, contoh: ```html

diff --git a/content/B-template-render-html.md b/content/B-template-render-html.md index 6490d5ba1..31f3ec9d4 100644 --- a/content/B-template-render-html.md +++ b/content/B-template-render-html.md @@ -1,8 +1,8 @@ # B.4. Template: Render HTML Template -Pada bagian ini kita akan belajar bagaimana cara render file **template** ber-tipe **HTML**, untuk ditampilkan pada browser. +Pada bagian ini kita akan belajar bagaimana cara render file **template** yang berisi **HTML** untuk ditampilkan ke layar browser. -Terdapat banyak jenis template pada Go, yang akan kita pakai adalah template HTML. Package `html/template` berisi banyak sekali fungsi untuk kebutuhan rendering dan parsing file template jenis ini. +Terdapat banyak jenis template pada Go, di sini yang akan kita pakai adalah template HTML. Package `html/template` berisi banyak sekali fungsi untuk operasi rendering dan parsing file template HTML. ## B.4.1. Struktur Aplikasi @@ -57,15 +57,15 @@ if err != nil { Package `path` berisikan banyak fungsi yang berhubungan dengan lokasi folder atau path, yang salah satu di antaranya adalah fungsi `path.Join()`. Fungsi ini digunakan untuk menggabungkan folder atau file atau keduanya menjadi sebuah path, dengan separator relatif terhadap OS yang digunakan. -> Separator yang digunakan oleh `path.Join()` adalah `\` untuk wind\*ws dan `/` untuk un\*x. +> Separator yang digunakan oleh `path.Join()` adalah `\` untuk windows dan `/` untuk linux/unix/macos. Contoh penerapan `path.Join()` bisa dilihat di kode di atas, `views` di-join dengan `index.html`, menghasilkan `views/index.html`. Sedangkan `template.ParseFiles()`, digunakan untuk parsing file template, dalam contoh ini file `view/index.html`. Fungsi ini mengembalikan 2 data, yaitu hasil dari proses parsing yang bertipe `*template.Template`, dan informasi `error` jika ada. -Fungsi `http.Error()` digunakan untuk menandai response (`http.ResponseWriter`) bahwa terjadi error, dengan kode error dan pesan error bisa ditentukan. Pada contoh di atas yang digunakan adalah **500 - internal server error** yang direpresentasikan oleh variabel `http.StatusInternalServerError`. +Fungsi `http.Error()` digunakan untuk menandai HTTP request dengan response berupa error dengan kode serta pesan error bisa kita tentukan sendiri. Pada contoh di atas yang digunakan adalah **500 - internal server error**, direpresentasikan oleh variabel `http.StatusInternalServerError`. -Method `Execute()` milik `*template.Template`, digunakan untuk menyisipkan data pada template, untuk kemudian ditampilkan ke browser. Data bisa disipkan dalam bentuk `struct`, `map`, atau `interface{}`. +Method `Execute()` milik `*template.Template`, digunakan untuk menyisipkan data pada template, kemudian menampilkannya ke browser. Data bisa disipkan ke view dalam bentuk `struct`, `map`, atau `interface{}`. - Jika dituliskan dalam bentuk `map`, maka **key** akan menjadi nama variabel dan **value** menjadi nilainya - Jika dituliskan dalam bentuk variabel objek cetakan `struct`, nama **property** akan menjadi nama variabel @@ -94,13 +94,13 @@ Tanda titik "\." pada \{\{\.namaVariabel\}\} menerangkan bahwa variabel tersebut ## B.4.4. Testing -Semua sudah siap, maka jalankan program lalu lakukan testing via browser. +Semua sudah siap, sekarang jalankan program, lalu lakukan testing di browser. ![Output HTML](images/B_template_render_html_2_output.png) ## B.4.5. Static File CSS -Kita akan coba tambahkan sebuah stylesheet di sini. Langsung saja, buat file statis `assets/site.css`, isi dengan kode berikut. +Coba tambahkan sebuah stylesheet di sini. Buat file `assets/site.css`, isi dengan kode berikut. ```css body { diff --git a/content/B-template-render-partial-html.md b/content/B-template-render-partial-html.md index 3100021cb..160292781 100644 --- a/content/B-template-render-partial-html.md +++ b/content/B-template-render-partial-html.md @@ -1,15 +1,15 @@ # B.5. Template: Render Partial HTML Template -Satu buah halaman yang berisikan html, bisa terbentuk dari banyak template html (parsial). Pada chapter ini kita akan belajar bagaimana membuat, mem-parsing, dan me-render semua file tersebut. +Satu buah halaman yang berisikan html bisa saja terbentuk dari lebih dari satu proses parsing template html (parsial). Pada chapter ini kita akan belajar bagaimana membuat, mem-parsing, dan me-render semua template file. -Ada beberapa metode yang bisa digunakan, dari ke semuanya akan kita bahas 2 di antaranya, yaitu: +Ada beberapa metode yang bisa digunakan, 2 di antaranya: - Menggunakan fungsi `template.ParseGlob()`. - Menggunakan fungsi `template.ParseFiles()`. ## B.5.1. Struktur Aplikasi -Mari langsung kita praktekan. Buat project baru, siapkan file dan folder dengan susunan seperti dengan gambar berikut. +Mari belajar sambil praktek seperti biasa. Buat project baru, siapkan file dan folder dengan susunan seperti dengan gambar berikut. ![Structure](images/B_template_render_partial_html_1_structure.png) @@ -39,11 +39,11 @@ func main() { Tipe `M` merupakan alias dari `map[string]interface{}`, disiapkan untuk mempersingkat penulisan tipe map tersebut. Pada pembahasan-pembahasan selanjutnya kita akan banyak menggunakan tipe ini. -Pada kode di atas, di dalam fungsi `main()`, fungsi `template.ParseGlob()` dipanggil, dengan parameter adalah pattern path `"views/*"`. Fungsi ini digunakan untuk memparsing semua file yang match dengan pattern yang ditentukan, dan fungsi ini mengembalikan 2 objek: `*template.Template` & `error`. +Pada kode di atas, di dalam fungsi `main()`, fungsi `template.ParseGlob()` dipanggil, dengan parameter adalah pattern path `"views/*"`. Fungsi ini digunakan untuk memparsing semua file yang match dengan *pattern*/pola yang ditentukan. Fungsi ini mengembalikan 2 objek yaitu `*template.Template` & `error`. > Pattern path pada fungsi `template.ParseGlob()` nantinya akan di proses oleh `filepath.Glob()` -Proses parsing semua file html dalam folder `views` dilakukan di awal, agar ketika mengakses rute-tertentu-yang-menampilkan-html, tidak terjadi proses parsing lagi. +Proses parsing semua file html dalam folder `views` dilakukan di awal, agar ketika suatu endpoint diakses nantinya tidak terjadi proses parsing melainkan hanya proses rendering saja. > Parsing semua file menggunakan `template.ParseGlob()` yang dilakukan di luar handler, tidak direkomendasikan dalam fase development. Karena akan mempersulit testing html. Lebih detailnya akan dibahas di bagian bawah. @@ -164,7 +164,7 @@ Bisa dilihat pada gambar di atas, ketika rute `/index` dan `/about` di akses, ko ## B.5.6. Parsing Banyak File HTML Menggunakan `template.ParseFiles()` -Metode parsing menggunakan `template.ParseGlob()` memiliki kekurangan yaitu sangat tergantung terhadap pattern path yang digunakan. Jika dalam suatu proyek terdapat sangat banyak file html dan folder, sedangkan hanya beberapa yang digunakan, pemilihan pattern path yang kurang tepat akan menjadikan file lain ikut ter-parsing dengan sia-sia. +Metode parsing menggunakan `template.ParseGlob()` memiliki kekurangan yaitu sangat tergantung terhadap pattern path yang digunakan. Jika dalam suatu proyek terdapat sangat banyak file html dan folder, sedangkan hanya beberapa yang digunakan, pemilihan pattern path yang kurang tepat akan menjadikan file lain ikut ter-parsing sia-sia. Dan juga, karena statement `template.ParseGlob()` dieksekusi diluar handler, maka ketika ada perubahan pada salah satu view, lalu halaman di refresh, output yang dihasilkan akan tetap sama. Solusi dari masalah ini adalah dengan memanggil `template.ParseGlob()` di tiap handler rute-rute yang diregistrasikan. @@ -210,7 +210,7 @@ Mari kita praktekan. Ubah handler rute `/index` dan `/about`. Gunakan `template. }) ``` - - **Hapus** statement `template.ParseGlob()`. + - Tak lupa **hapus** statement `template.ParseGlob()`. ```go var tmpl, err = template.ParseGlob("views/*") diff --git a/content/C-dockerize-golang.md b/content/C-dockerize-golang.md index 72e392af3..f75db6512 100644 --- a/content/C-dockerize-golang.md +++ b/content/C-dockerize-golang.md @@ -302,11 +302,11 @@ docker container run --name my-container-hello-world --rm -it -e PORT=8080 -e IN #### ◉ Flag `--rm` -Flag ini digunakan untuk meng-automatisasi proses penghapusan container sewaktu container tersebut di stop. Jadi kita tidak perlu delete manual pakai `docker container rm`. Hal ini sangat membantu karena *command* `docker run` akan membuat container baru setiap dijalankan. Tapi sebenarnya pada contoh sebelumnya kita tidak perlu khawatir akan dibuat container baru karena sudah ada flag `--name`. Flag tersebut digunakan untuk menentukan nama container, yang di mana nama container harus unik. Jadi kalau ada duplikasi pasti langsung error. Nah dari sini berarti kalau pembaca tidak pakai `--name` sangat dianjurkan paka `--rm` dalam penerapan `docker run`. +Flag ini digunakan untuk meng-automatisasi proses penghapusan container sewaktu container tersebut di stop. Jadi kita tidak perlu delete manual pakai `docker container rm`. Hal ini sangat membantu karena *command* `docker run` akan membuat container baru setiap dijalankan. Tapi sebenarnya pada contoh sebelumnya kita tidak perlu khawatir akan dibuat container baru karena sudah ada flag `--name`. Flag tersebut digunakan untuk menentukan nama container, yang mana nama container harus unik. Jadi kalau ada duplikasi pasti langsung error. Nah dari sini berarti kalau pembaca tidak pakai `--name` sangat dianjurkan paka `--rm` dalam penerapan `docker run`. #### ◉ Flag `-it` -Flag ini merupakan flag gabungan antara `-i` yang digunakan untuk meng-enable *interactive mode* dan `-t` untuk *enable* `TTY`. Dengan ini kita bisa masuk ke mode interaktif yang di mana jika kita terminate atau kill command menggunakan `CTRL + C` atau `CMD + C` (untuk mac), maka otomatis container akan di stop. +Flag ini merupakan flag gabungan antara `-i` yang digunakan untuk meng-enable *interactive mode* dan `-t` untuk *enable* `TTY`. Dengan ini kita bisa masuk ke mode interaktif yang mana jika kita terminate atau kill command menggunakan `CTRL + C` atau `CMD + C` (untuk mac), maka otomatis container akan di stop. Nah dengan menggabungkan flag `--rm` dan flag `-it` kita bisa dengan mudah stop kemudian hapus container. diff --git a/content/C-echo-template-rendering.md b/content/C-echo-template-rendering.md index 1af2e6e9c..2603b1815 100644 --- a/content/C-echo-template-rendering.md +++ b/content/C-echo-template-rendering.md @@ -4,7 +4,7 @@ Pada chapter ini kita akan belajar cara render template html pada aplikasi yang Pada dasarnya proses parsing dan rendering template tidak di-handle oleh echo sendiri, melainkan oleh API dari package `html/template`. Jadi bisa dibilang cara render template di echo adalah sama seperti pada aplikasi yang murni menggunakan golang biasa, seperti yang sudah dibahas pada chapter [Template: Render HTML Template](/B-template-render-html.html), [Template: Render Partial HTML Template](/B-template-render-partial-html.html), [Template: Render Specific HTML Template](/B-render-specific-html.html), dan [Template: Render HTML String](/B-render-html-string.html). -Echo menyediakan satu fasilitas yang bisa kita manfaatkan untuk standarisasi rendering template. Cara penggunaannya, dengan meng-override default `.Renderer` property milik echo menggunakan objek cetakan struct, yang di mana pada struct tersebut harus ada method bernama `.Render()` dengan skema sesuai dengan kebutuhan echo. Nah, di dalam method `.Render()` inilah kode untuk parsing dan rendering template ditulis. +Echo menyediakan satu fasilitas yang bisa kita manfaatkan untuk standarisasi rendering template. Cara penggunaannya, dengan meng-override default `.Renderer` property milik echo menggunakan objek cetakan struct, yang mana pada struct tersebut harus ada method bernama `.Render()` dengan skema sesuai dengan kebutuhan echo. Nah, di dalam method `.Render()` inilah kode untuk parsing dan rendering template ditulis. ## C.7.1. Praktek diff --git a/content/C-golang-sso-saml-sp.md b/content/C-golang-sso-saml-sp.md index a76b0e0ab..583afbd6b 100644 --- a/content/C-golang-sso-saml-sp.md +++ b/content/C-golang-sso-saml-sp.md @@ -272,7 +272,7 @@ http.ListenAndServe(portString, nil) go run *.go ``` -Oops, muncul error pada saat mengakses `http://localhost:9000/index`. Meski url ini merupakan protected url, yang di mana hanya bisa diakses ketika sudah login, harusnya user akan di-redirect ke halaman login, bukan malah memunculkan error. +Oops, muncul error pada saat mengakses `http://localhost:9000/index`. Meski url ini merupakan protected url, yang mana hanya bisa diakses ketika sudah login, harusnya user akan di-redirect ke halaman login, bukan malah memunculkan error. ![Metadata share](images/C_golang_sso_saml_sp_1_saml_metadata_missing.png) diff --git a/content/C-https-tls.md b/content/C-https-tls.md index 805ec65b3..e8726619a 100644 --- a/content/C-https-tls.md +++ b/content/C-https-tls.md @@ -126,7 +126,7 @@ Coba test juga menggunakan browser, jika menggunakan chrome maka akan muncul war ![browser example](images/C_https_tls_5_browser_example.png) -Warning `NET::ERR_CERT_AUTHORITY_INVALID` muncul ketika mengakses sebuah website menggunakan protokol `https` yang di mana website ini mengaplikasikan **self-signed certificate**, bukan menggunakan certificate yang sudah diverifikasi oleh CA. +Warning `NET::ERR_CERT_AUTHORITY_INVALID` muncul ketika mengakses sebuah website menggunakan protokol `https` yang mana website ini mengaplikasikan **self-signed certificate**, bukan menggunakan certificate yang sudah diverifikasi oleh CA. --- diff --git a/content/D-insert-1mil-csv-record-into-db-in-a-minute.md b/content/D-insert-1mil-csv-record-into-db-in-a-minute.md index a06585d84..cb51c12e2 100644 --- a/content/D-insert-1mil-csv-record-into-db-in-a-minute.md +++ b/content/D-insert-1mil-csv-record-into-db-in-a-minute.md @@ -16,7 +16,7 @@ Dengan metode worker pool ini, maka penggunaan memory dan performansi program ak *Connection pool* adalah metode untuk manajemen sejumlah koneksi database, agar bisa digunakan secara optimal. -Connection pool sangat penting dalam kasus operasi data yang berhubungan dengan database yang di mana concurrent programming diterapkan. +Connection pool sangat penting dalam kasus operasi data yang berhubungan dengan database yang mana concurrent programming diterapkan. Karena pada concurrent programming, beberapa proses akan berjalan bersamaan, maka penggunaan 1 koneksi db akan menghambat proses tersebut. Perlu ada beberapa koneksi database, agar goroutine tidak rebutan objek koneksi database.