고랭 라이브러리 Lorca 를 이용하여 카운터 페이지를 만들어보자.
예제 깃헙 페이지에서 Lorca 에서 함수를 이용하는 방법에 대하여 알아 볼 수있다.
//go:embed index.html
var fs embed.FS
type counter struct {
sync.Mutex
count int
}
고랭의 임베드를 활용하여 index.html
파일을 변수에 포함한다.
// go:embed [파일이름]
이란 주석을 다는 것만으로 그 내용을 변수에 저장할 수 있다.
개발자가 할 일은 embed 를 go build 하는 것 뿐이다.
파일을 읽을 때 표기법이 잘못되었거나 읽을 수 없을 때 컴파일 오류가 발생한다.
embed.FS
형태의 파일 시스템 뿐만 아니라 바이트나 문자열로도 사용할 수 있다.
UI 에 바인딩된 고 타입은 스레드에 안전해야 한다. 각각 바인딩된 것 자체로 고루틴에서 실행되기 때문인데, 그래서 counter
구조체에서 뮤텍스를 사용한다.
위 경우에서 원자성을 사용할 수 있게 된다.
하지만 보다 복잡한 경우엔 적절한 동기화를 사용하자.
func (c *counter) Add(n int) {
c.Lock()
defer c.Unlock()
c.count = c.count + n
}
func (c *counter) Value() int {
c.Lock()
defer c.Unlock()
return c.count
}
Add
리시버는 스레드에 안전하게 잠금 풀기를 하여 count
에 n
을 더한다.
Value
리시버도 마찬가지로 안전하게 잠금 풀기를 하여 count
를 반환한다.
이제 인덱스 UI 를 띄우며, 카운터 객체를 바인딩해보자.
func Couter() {
...
}
Lorca 를 띄워보자.
args := []string{}
if runtime.GOOS == "linux" {
args = append(args, "--class=Lorca")
}
ui, err := lorca.New("", "", 480, 320, args...)
if err != nil {
log.Fatal(err)
}
defer ui.Close()
lorca.New
를 통해 마지막 인수에 각종 정보를 전달한다.
두 번째 줄을 확인하면 사용자의 운영체제가 리눅스일 경우 --class=Lorca
정보를 전달한다.
추가적으로 GUI 의 너비와 높이를 지정하고 defer
을 통해 연기된 Close()
를 실행한다.
ui.Bind("start", func() {
log.Println("UI is ready")
})
c := &counter{}
ui.Bind("counterAdd", c.Add)
ui.Bind("counterValue", c.Value)
형식은 ui.Bind(name string, f interface{})
와 같으며
name
은 js 에서의 함수이름으로 사용할 변수 이름이다.
그래서 위와 같은 경우는 "start"
로 UI is ready
로그 출력
"counterAdd"
로 c.Add
, "couterValue"
로 c.Value
를 바인드한다.
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
defer ln.Close()
go http.Serve(ln, http.FileServer(http.FS(fs)))
ui.Load(fmt.Sprintf("http//%s/index.html", ln.Addr()))
앞서 Lorca의 작동원리로 설명한 바가 있듯이, 크롬을 통하여 원격 바인딩하여 소켓을 통해 주고받기 때문에 net.Listen
함수로 네트워크 연결을 한다.
그럼 Lorca 가 사용자의 설치된 크롬을 통하여 주고받는다.
그 후 http.Serve
함수를 통해 해당 네트워크로 HTTP 연결을 수락하고 새로운 고루틴을 만든다. 그리고 이전에 임베드로 저장해 두었던 index.html
파일로의 경로로 리다이렉션한다.
그리고 이전에 생성하였던 ui
에 해당 주소의 내용을 로드한다.
ui.Eval(`
console.log("Hello, world!");
console.log('Multiple values:', [1, false, {"x":5}]);
`)
위와 같이 자바스크립트 문법을 Eval
함수를 통하여 JS 코드를 디버깅도 할 수 있다.
sigc := make(chan os.Signal)
signal.Notify(sigc, os.Interrupt)
select {
case <-sigc:
case <-ui.Done():
}
채널을 생성하여 인터럽트되거나 UI 를 종료할때 까지 기다린다.
<!doctype html>
<html>
<head>
...
</head>
<body onload=start()>
...
</body>
</html>
위 body 에서의 onload=start()
를 살펴보면 앞서 바인드 하였던 start
를 호출하고 있는 것을 볼 수 있다.
앱을 실행하면 위와 같이 body 가 로드되면서 go 의 start
가 실행되었다는 것을 확인할 수 있다.
<script>
const counter = document.querySelector('.counter');
const btnIncr = document.querySelector('.btn-incr');
const btnDecr = document.querySelector('.btn-decr');
// GO 기능이 비동기적이기 때문에 async/await 를 사용한다.
const render = async () => {
counter.innerText = `Count: ${await counterValue()}`;
};
btnIncr.addEventListener('click', async () => {
await counterAdd(1); // Call Go function
render();
});
btnDecr.addEventListener('click', async () => {
await counterAdd(-1); // Call Go function
render();
});
render();
</script>
위의 코드도 동일하게 클릭 이벤트 리스너 내에서 go 에서 바인딩되었던 함수를 호출하고 있는 것을 확인할 수 있다.
go 의 기능은 비동기적이기 때문에 자바스크립트에서도 동일하게 비동기를 사용하기 위해 async/await
키워드를 사용해야 한다.