Kode program : HttpRouter

Golang – Pengenalan HttpRouter

HttpRouter merupakan salah satu, OpenSource library yang populer untuk Http Handler di golang, HttpRouter terkenal dengan kecepatannya dan sangat minimalis. Hal ini dikarenakan HttpRouter hanya memiliki fitur routing saja, tidak memiliki fitur apapun selain itu. Untuk detail nya bisa dilihat di https://github.com/julienschmidt/httprouter

Pada artikel kali ini kita akan menggunakan HttpRouter sebagai handler dan Testify untuk memudahkan kita dalam membuat unit test. Untuk menambahkannya bisa menggunakan command berikut

$ go get github.com/julienschmidt/httprouter
$ go get github.com/stretchr/testify

Untuk mencoba HttpRouter ini, silakan buat project baru dengan nama belajar-golang-httprouter dan tambahkan module dengan command diatas.

Router

Inti dari library HttpRouter adalah struct Router, Router ini merupakan implementasi dari http.Handler, sehingga kita bisa dengan mudah menambahkannya ke dalam http.Server. Untuk membuat router, kita bisa menggunakan function httprouter.New(), yang akan mengembalikan Router pointer.

HTTP Method

Router mirip dengan ServeMux, dimana kita bisa menambahkan route ke Router, kelebihan dibandingkan ServeMux adalah, pada Router kita bisa menentukan HTTP Method yang ingin kita gunakan, misal GET, POST, PUT, dan lain-lain. Cara menambahkan route kedalam Router adalah dengan menggunakan function yang sama dengan HTTP Method nya, misal router.GET(), router.POST, dan lain-lain.

httprouter.Handle

Saat kita menggunakan ServeMux, kita menambahkan route kita bisa menggunakan http.Handler. Berbeda dengan Router, kita tidak menggunakan http.Handler lagi, melainkan menggunakan type httprouter.Handle, perbedaan dengan http.Handler adalah, pada httprouter.Handle, terdapat parameter ketiga yaitu Params, yang akan kita bahan nanti.

Buatlah file baru dengan nama main.go dan masukkan baris kode berikut

package main

import (
	"fmt"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func main() {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		fmt.Fprint(w, "Hello HttpRouter")
	})

	server := http.Server{
		Addr:    "localhost:8080",
		Handler: router,
	}

	err := server.ListenAndServe()
	if err != nil {
		panic(err)
	}
}
Kode program : HttpRouter
Kode program : HttpRouter

Sebenarnya ini sama saja dengan menggunakan ServeMux yang sudah kita bahas di materi Golang Web, yang membedakan hanyalah saat membuat handler kita bisa menentukan HTTP Method yang akan digunakan dan parameter nya saat ini ada tiga, seperti yang ada di baris ke 12.

Selanjutnya kita coba buat unit test nya, buatlah file baru dengan nama router_test.go dan isikan baris kode berikut

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

func TestRouter(t *testing.T) {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		fmt.Fprint(w, "Hello World")
	})

	request := httptest.NewRequest("GET", "http://localhost:8080/", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Hello World", string(body))
}
Kode program : Unit test HttpRouter
Kode program : Unit test HttpRouter

Params

httprouter.Handle memiliki parameter yang ketiga, yaitu params. Params merupakan tempat untuk meyimpan parameter yang dikirim dari client, namun params ini bukan query parameter, melainkan parameter di URL.

Kadang kita butuh membuat URL yang tidak fix, alias bisa berubah-ubah, misal /products/1, /products/2dan seterusnya.ServeMux tidak mendukung hal tersebut, namun Router mendukung hal tersebut.

Parameter yang dinamis yang terdapat di URL, secara otomatis dikumpulkan di params, namun agar Router tahu, kita harus memberi tahu ketika menambahkan Route, dibagian mana kita akan buat URL path nya menjadi dinamis.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

func TestParams(t *testing.T) {
	router := httprouter.New()
	router.GET("/products/:id", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		id := p.ByName("id")
		text := "Product " + id
		fmt.Fprint(w, text)
	})

	request := httptest.NewRequest(http.MethodGet, "http://localhost:8080/products/1", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Product 1", string(body))
}
Kode program : HttpRouter dengan params
Kode program : HttpRouter dengan params

Dengan adanya fitur params, kita jadi bisa membuat URL dinamis, seperti pada baris ke 16, kita hanya perlu memberi tahu router dengan memasukkan :paramName pada URL, dan untuk mengambil nya kita bisa gunakan method ByName(paramName).

Router Pattern

Sekarang kita sudah tau bahwa dengan menggunakan Router, kita bisa menambahkan params di URL, sekarang pertanyaannya, bagaimana pattern(pola) pembuatan parameter nya ?

Yang pertama ada yang namanya Named Parameter :

Named parameter adalah pola pembuatan parameter dengan menggunakan nama, setiap nama parameter harus diawali dengan : (titik dua), lalu diikuti dengan nama parameter, contoh :

Pattern/user/:user
/user/rendymatch
/user/youmatch
/user/rendy/profileno match
/user/no match
Named Parameter

Selain named parameter, ada juga yang bernama Catch All Parameter, yaitu menangkap semua parameter. Catch all parameter harus diawali dengan * (bintang), lalu diikuti dengan nama parameter, dan catch all parameter harus berada di akhi URL

Pattern/src/*filepath
/src/no match
/src/somefilematch
/src/subdir/somefilematch
Catch All Parameter

Pertama kita akan mencoba named parameter terlebih dahulu, buatlah file baru dengan nama router_pattern_test.go

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

func TestPatternNamedParameter(t *testing.T) {
	router := httprouter.New()
	router.GET("/products/:id/items/:itemId", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		id := p.ByName("id")
		itemId := p.ByName("itemId")
		text := "Product " + id + " Item " + itemId
		fmt.Fprint(w, text)
	})

	request := httptest.NewRequest(http.MethodGet, "http://localhost:8080/products/1/items/1", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Product 1 Item 1", string(body))
}
Kode program : HttpRouter dengan named parameter
Kode program : HttpRouter dengan named parameter

Selanjutnya untuk catch all parameter, contohnya adalah seperti berikut :

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

func TestPatternCatchAllParameter(t *testing.T) {
	router := httprouter.New()
	router.GET("/images/*image", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		image := p.ByName("image")
		text := "Image : " + image
		fmt.Fprint(w, text)
	})

	request := httptest.NewRequest(http.MethodGet, "http://localhost:8080/images/small/profile.png", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Image : /small/profile.png", string(body))
}
Kode program : HttpRouter dengan catch all parameter
Kode program : HttpRouter dengan catch all parameter

Serve File

Pada materi golang web kita sudah pernah membahas tentang Serve File, pada router pun mendung serve static file menggunakan function ServeFiles(Path, FileSystem), dimana pada path kita harus menggunakan catch all parameter. Sedangkan pada file system kita bisa melakukan manual load dari folder atau menggunakan golang embed, seperti yang pernah kita bahas di materi golang web.

Buatlah folder baru dengan nama resources dan di dalam folder tersebut buat juga file baru dengan nama hello.txt dan isikan Hello from HttpRouter serve file. Selanjutnya buat juga file baru di root project dengan nama serve_file_test.go dan isikan baris kode berikut.

package main

import (
	"embed"
	"io"
	"io/fs"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

//go:embed resources
var resources embed.FS

func TestServeFile(t *testing.T) {
	router := httprouter.New()
	directory, _ := fs.Sub(resources, "resources")
	router.ServeFiles("/files/*filepath", http.FS(directory))

	request := httptest.NewRequest("GET", "http://localhost:8080/files/hello.txt", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Hello from HttpRouter serve file", string(body))
}
Kode program : Serve file dangan HttpRouter
Kode program : Serve file dangan HttpRouter

Panic Handler

Apa yang terjadi jika terjadi panic pada logic Handler yang kita buat?

Secara otomatis akan terjadi error, dan web akan berhenti mengembalikan response. Kadang saat terjadi panic, kita ingin melakukan sesuatu, misal memberitahu jika terjadi kesalahan di web, atau bahkan mengirim informasi log kesalahan yang terjadi. Sebelumnya, seperti yang sudah kita bahas di materi golang web, jika kita ingin menangani panic, kita harus membuat Middleware khusus secara manual.

Namun di Router, sudah di sediakan untuk menangani panic, caranya menggunakan attribute PanicHandler func(http.ResponseWriter, *http.Request, interface{})

Buatlah file baru panic_test.go dan isikan baris kode berikut :

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

func TestPanicHandler(t *testing.T) {
	router := httprouter.New()

	router.PanicHandler = func(w http.ResponseWriter, r *http.Request, err interface{}) {
		fmt.Fprint(w, "Panic : "+err.(string))
	}

	router.GET("/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		panic("Ups")
	})

	request := httptest.NewRequest("GET", "http://localhost:8080/", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Panic : Ups", string(body))
}
Kode program : Panic handler pada HttpRouter
Kode program : Panic handler pada HttpRouter

Not Found Handler

Selain panic handler, Router juga memiliki not found handler. Not Found handler adalah handler yang dieksekusi ketika client mencoba request URL yang memang tidak terdapat di Router.

Secara default, jika tidak ada router ditemukan, Router akan melanjutkan request ke http.NotFound, namun kita bisa mengubahnya, caranya dengan mengubah router.NotFound = http.Handler.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

func TestNotFound(t *testing.T) {
	router := httprouter.New()
	router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Gak Ketemu")
	})

	request := httptest.NewRequest("GET", "http://localhost:8080/", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Gak Ketemu", string(body))
}
Kode program : Not found handler pada HttpRouter
Kode program : Not found handler pada HttpRouter

Method Not Allowed Handler

Saat menggunakan ServeMux, kita tidak bisa menentukan HTTP Method apa yang digunakan untuk Handler, namun pada Router, kita bisa menentukan HTTP Method yang ingin kita gunakan, lantas apa yang terjadi jika client tidak mengirim HTTP Method sesuai dengan yang kita tentukan?

Maka akan terjadi error Method Not Allowed, secara default jika terjadi error seperti ini maka Router akan memanggil function http.Error. Jika kita ingin merubahnya, kita bisa gunakan router.MethodNotAllowed = http.Handler.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

func TestMethodNotAllowed(t *testing.T) {
	router := httprouter.New()
	router.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Gak Boleh")
	})
	router.POST("/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		fmt.Fprint(w, "POST")
	})

	request := httptest.NewRequest("GET", "http://localhost:8080/", nil)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Gak Boleh", string(body))
}
Kode program : Method not allowed handler pada HttpRouter
Kode program : Method not allowed handler pada HttpRouter

Middleware

HttpRouter hanyalah library untuk http router saja, tidak ada fitur lain selain router, dan karena Router merupakan implementasi dari http.Handler, jadi untuk middleware kita bisa membuat sendiri secara manual seperti yang sudah kita bahas pada materi golang web.

Buatlah file baru dengan nama middleware_test.go

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

type LogMiddleware struct {
	http.Handler
}

func (middleware *LogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Receive Request")
	middleware.Handler.ServeHTTP(w, r)
}

func TestMiddleware(t *testing.T) {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		fmt.Fprint(w, "Hello World")
	})

	middleware := LogMiddleware{router}

	request := httptest.NewRequest("GET", "http://localhost:8080/", nil)
	recorder := httptest.NewRecorder()

	middleware.ServeHTTP(recorder, request)

	response := recorder.Result()
	body, _ := io.ReadAll(response.Body)

	assert.Equal(t, "Hello World", string(body))
}
Kode program : Middleware pada HttpRouter
Kode program : Middleware pada HttpRouter

Penutup

Pada artikel kali ini kita telah belajar tentang http router pada golang web. Dan pada artikel selanjutnya saya akan membahas json pada golang web.

Leave a reply:

Your email address will not be published.

Site Footer

Sliding Sidebar

About Me

About Me

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam.

Social Profiles

Facebook