[Ubuntu] apache2のvirtualhostの反映 (vps)

vps(さくらvps)でapache2のvirtualhostの反映を設定します。

sudo vi /etc/apache2/sites-available/virtual.host.conf

<VirtualHost *:80>
DocumentRoot /var/www/app
ServerName sample-test.site
ServerAlias www.sample-test.site
# Other directives here

</VirtualHost>

$ sudo systemctl reload apache2
$ sudo systemctl restart apache2
-> これだと000-default.confの設定が優先されるので、反映されない

$ sudo a2dissite 000-default.conf
$ sudo a2ensite virtual.host.conf
$ sudo systemctl reload apache2

なるほど
ここから、サブドメインを設定する

[RDS] snapshotから別のDBを起動する

### 1. RDS作成
まず、RDSでpostgreSQLを作成し、EC2から接続してデータを挿入します。

$ psql -h xxxxxx.*.ap-northeast-1.rds.amazonaws.com -U root -d postgres

CREATE TABLE playground (
    equip_id serial PRIMARY KEY,
    type varchar (50) NOT NULL,
    color varchar (25) NOT NULL,
    location varchar(25) check (location in ('north', 'south', 'west', 'east', 'northeast', 'southeast', 'southwest', 'northwest')),
    install_date date
);

INSERT INTO playground (type, color, location, install_date) VALUES ('slide', 'blue', 'south', '2022-04-28');
INSERT INTO playground (type, color, location, install_date) VALUES ('swing', 'yellow', 'northwest', '2022-08-16');

postgres=> SELECT * FROM playground;

### 2. snapshotsからrestore
RDSのsnapshotsでsnapshotsを作成し、少し時間を置いてから、restoreを押下します。

するとDB作成時と同じ様な設定画面が表示される。

create databaseをすると元のDBとは別のDBが作成される

### restoreしたRDSに接続
endpointをコピーして、新しいDBの方に接続
$ psql -h xxxxxx.*.ap-northeast-1.rds.amazonaws.com -U root -d postgres
postgres=> SELECT * FROM playground;
equip_id | type | color | location | install_date
———-+——-+——–+———–+————–
1 | slide | blue | south | 2022-04-28
2 | swing | yellow | northwest | 2022-08-16
(2 行)

うおおおおおおおおおおおおおおお
これは凄い
完全に理解した

[Docker] コンテナのディレクトリ・ファイルをローカル環境(ホスト)に取得

ファイルのコピーは、コンテナ⇆ホスト どちらもdocker cpで行う

以下の様にループで回すこともできるが、

for f in $(sudo docker exec -it vigorous_shannon bash -c "ls /opt/app/*"); do sudo docker cp vigorous_shannon:$f /home/vagrant/dev/test; done

この様なエラーになる
Error: No such container:path: vigorous_shannon:models.py

そのため、ディレクトリを丸ごとコピーした方が早い

sudo docker cp vigorous_shannon:/opt/app /home/vagrant/dev/test

おっけーーーーーーー
さて、作業するか

[Sass] media queryを変数で書いて重複をなくしたい

HTML: チャット画面をBluma CSSでinputとsubmitを書いているが、classのis-expandが横幅maxにならないので、CSSで横幅を指定する。その際に、レスポンシブルでスマホはwidth85%, desktopはwidth65%としたい。

			<div class="field is-grouped" id="newMessage">
			  <p class="control is-expanded" >
				<input type="text" id="message" class="input">
			  </p>
			  <p class="control">
			    <input type="submit" value="send" class="button is-link">
			  </p>
			</div>

画面

### 従来のmedia queryで書く場合
style.scss: これでも動くには動くが、プログラマーとしては重複して書くのはかなり気持ちが悪い

@media screen and (min-width: 768px) {
	#newMessage {
		position: fixed;
		width:65%;
		bottom: 30px;	
	}
}
@media screen and (max-width: 768px) {
    #newMessage {
		position: fixed;
		width:85%;
		bottom: 30px;	
	}
}

### Sassで変数を使って書く場合
media queryで表示を分けて書く箇所が多い場合はかなり重宝できる

$breakpoints: (
	'sm': 'screen and (max-width: 768px)',
	'md': 'screen and (min-width: 768px)',
) !default;

@mixin mq($breakpoint: md) {
	@media #{map-get($breakpoints, $breakpoint)}{
		@content;
	}
}
#newMessage {
	position: fixed;
	@include mq(sm) { 
    	width: 85%
  	}
  	@include mq(md) { 
    	width: 65%
  	}
	bottom: 30px;	
}

webpackでコンパイルされると、以下のように出力されている

あら、 かなり良いですね~
寿司食べたい🍣

[Go言語] revelでchatを作る

conf/routes

GET		/websocket/room		  WebSocket.Room
WS      /websocket/room/socket   WebSocket.RoomSocket 

app/views/WebSocket/room.html

{{set . "title" "Chat room"}}
{{template "header.html" .}}

<h1>WebSocket - You are now chatting as {{.user}} <a href="/">Leave</a></h1>

<div id="thread">
	<script type="text/html" id="message_tmpl">
		{{raw "<%"}} if(event.Type == 'message') { %>
			<div class="message <%= event.User == '{{.user}}' ? 'you' : '' %>">
				<h2>{{raw "<%"}}= event.User %></h2>
				<p>
					{{raw "<%"}}= event.Text %>
				</p>
			</div>
		{{raw "<%"}} } %>
		{{raw "<%"}} if(event.Type == 'join') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> joined the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'leave') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> left the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'quit') { %>
			<div class="message important">
				<h2></h2>
				<p>
					You are disconnected!
				</p>
			</div>
		{{raw "<%"}} }  %>
	</script>
</div>

<div id="newMessage">
	<input type="text" id="message" autocomplete="off" autofocus>
	<input type="submit" value="send" id="send">
</div>

<script type="text/javascript">

	var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

	var display = function(event){
		$('#thread').append(tmpl('message_tmpl', {event: event}));
		$('#thread').scrollTo('max')
	}

	socket.onmessage = function(event){
		display(JSON.parse(event.data))
	}

	$('#send').click(function(e){
		var message = $('#message').val()
		$('#message').val('')
		socket.send(JSON.stringify(message))
	})

	$('#message').keypress(function(e){
		if(e.charCode == 13 || e.keyCode == 13){
			$('#send').click()
			e.preventDefault()
		}
	})
</script>

app/views/header.html

    <script src="/public/js/jquery.scrollTo-min.js"></script>
    <script src="/public/js/templating.js"></script>

chatroom.go

package chatroom

import (
	"container/list"
	"time"
)

type Event struct {
	Type string
	User string
	Timestamp int
	Text string
}

type Subscription struct {
	Archive []Event
	New <-chan Event
}

func (s Subscription) Cancel(){
	unsubscribe <- s.New
	drain(s.New)
}

func newEvent(typ, user, msg string) Event {
	return Event{typ, user, int(time.Now().Unix()), msg}
}

func Subscribe() Subscription {
	resp := make(chan Subscription)
	subscribe <- resp
	return <- resp
}

func Join(user string){
	publish <- newEvent("join", user, "")
}

func Say(user, message string){
	publish <- newEvent("message", user, message)
}

func Leave(user string){
	publish <- newEvent("leave", user, "")
}

const archiveSize = 10

var (
	subscribe = make(chan (chan<- Subscription), 10)

	unsubscribe = make(chan (<-chan Event), 10)
	publish = make(chan Event, 10)
)

func chatroom(){
	archive := list.New()
	subscribers := list.New()

	for {
		select {
		case ch := <-subscribe:
			var events []Event
			for e := archive.Front(); e != nil; e = e.Next(){
				events = append(events, e.Value.(Event))
			}
			subscriber := make(chan Event, 10)
			subscribers.PushBack(subscriber)
			ch <- Subscription{events, subscriber}
		case event := <-publish:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next(){
				ch.Value.(chan Event) <- event
			}
			if archive.Len() >= archiveSize {
				archive.Remove(archive.Front())
			}
			archive.PushBack(event)
		case unsub := <-unsubscribe:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next(){
				if ch.Value.(chan Event) == unsub {
					subscribers.Remove(ch)
					break
				}
			}
		}
	}
}

func init(){
	go chatroom()
}

func drain(ch <-chan Event){
	for {
		select {
		case _, ok := <-ch:
			if !ok {
				return
			}
		default:
			return
		}
	}
}

websocket.go

package controllers

import (
	"github.com/revel/revel"
	"app/app/chatroom"
)

type WebSocket struct {
	*revel.Controller
}

func (c WebSocket) Room(user string) revel.Result {
	user = "hpscript"
	return c.Render(user)
}

func (c WebSocket) RoomSocket(user string, ws revel.ServerWebSocket) revel.Result {

	if ws == nil {
		return nil
	}

	subscription := chatroom.Subscribe()
	defer subscription.Cancel()

	chatroom.Join(user)
	defer chatroom.Leave(user)

	for _, event := range subscription.Archive {
		if ws.MessageSendJSON(&event) != nil {
			return nil
		}
	}

	newMessages := make(chan string)
	go func(){
		var msg string
		for {
			err := ws.MessageReceiveJSON(&msg)
			if err != nil {
				close(newMessages)
				return
			}
			newMessages <- msg
		}
	}()

	for {
		select {
		case event := <-subscription.New:
			if ws.MessageSendJSON(&event) != nil {
				return nil
			}
		case msg, ok := <-newMessages:
			if !ok {
				return nil
			}
			chatroom.Say(user, msg)
		}
	}
	return nil
}

public/js/templating.js

{{set . "title" "Chat room"}}
{{template "header.html" .}}

<h1>WebSocket - You are now chatting as {{.user}} <a href="/">Leave</a></h1>

<div id="thread">
	<script type="text/html" id="message_tmpl">
		{{raw "<%"}} if(event.Type == 'message') { %>
			<div class="message <%= event.User == '{{.user}}' ? 'you' : '' %>">
				<h2>{{raw "<%"}}= event.User %></h2>
				<p>
					{{raw "<%"}}= event.Text %>
				</p>
			</div>
		{{raw "<%"}} } %>
		{{raw "<%"}} if(event.Type == 'join') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> joined the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'leave') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> left the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'quit') { %>
			<div class="message important">
				<h2></h2>
				<p>
					You are disconnected!
				</p>
			</div>
		{{raw "<%"}} }  %>
	</script>
</div>

<div id="newMessage">
	<input type="text" id="message" autocomplete="off" autofocus>
	<input type="submit" value="send" id="send">
</div>

<script type="text/javascript">

	var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

	var display = function(event){
		$('#thread').append(tmpl('message_tmpl', {event: event}));
		$('#thread').scrollTo('max')
	}

	socket.onmessage = function(event){
		display(JSON.parse(event.data))
	}

	$('#send').click(function(e){
		var message = $('#message').val()
		$('#message').val('')
		socket.send(JSON.stringify(message))
	})

	$('#message').keypress(function(e){
		if(e.charCode == 13 || e.keyCode == 13){
			$('#send').click()
			e.preventDefault()
		}
	})
</script>

<% ●● %>: 構文
<%= ■■ %>: 出力

これは凄し

[Go言語] revelのexamplesのchatを覗いてみる

examplesのgit: https://github.com/revel/examples

$ git clone https://github.com/revel/examples.git
$ revel run github.com/revel/examples/chat

templateのjs

  var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

  // Display a message
  var display = function(event) {
    $('#thread').append(tmpl('message_tmpl', {event: event}));
    $('#thread').scrollTo('max')
  }

  // Message received on the socket
  socket.onmessage = function(event) {
    display(JSON.parse(event.data))
  }

  $('#send').click(function(e) {
    var message = $('#message').val()
    $('#message').val('')
    socket.send(JSON.stringify(message))
  });

  $('#message').keypress(function(e) {
    if(e.charCode == 13 || e.keyCode == 13) {
      $('#send').click()
      e.preventDefault()
    }
  })

route

GET     /websocket/room                         WebSocket.Room
WS      /websocket/room/socket                  WebSocket.RoomSocket

top

<form action="{{url "Application.EnterDemo"}}">
      {{if .flash.error}}
        <p class="error">
          {{.flash.error}}
        </p>
      {{end}}
      <p>
        <label for="nick">Choose a nick name</label>
        <input type="text" name="user" id="user">
      </p>
      <p>
        <label for="nick">Demonstration</label>
        <select name="demo">
          <option></option>
          <option value="refresh">Ajax, active refresh</option>
          <option value="longpolling">Ajax, long polling</option>
          <option value="websocket">WebSocket</option>
        </select>
      </p>
      <p>
        <label></label>
        <input type="submit" id="enter" value="Enter the chat room">
      </p>
    </form>

controller

func (c Application) EnterDemo(user, demo string) revel.Result {
	c.Validation.Required(user)
	c.Validation.Required(demo)

	if c.Validation.HasErrors() {
		c.Flash.Error("Please choose a nick name and the demonstration type.")
		return c.Redirect(Application.Index)
	}

	switch demo {
	case "refresh":
		return c.Redirect("/refresh?user=%s", user)
	case "longpolling":
		return c.Redirect("/longpolling/room?user=%s", user)
	case "websocket":
		return c.Redirect("/websocket/room?user=%s", user)
	}
	return nil
}

controller

package controllers

import (
	"github.com/revel/revel"

	"github.com/revel/examples/chat/app/chatroom"
)

type WebSocket struct {
	*revel.Controller
}

func (c WebSocket) Room(user string) revel.Result {
	return c.Render(user)
}

func (c WebSocket) RoomSocket(user string, ws revel.ServerWebSocket) revel.Result {
	// Make sure the websocket is valid.
	if ws == nil {
		return nil
	}

	// Join the room.
	subscription := chatroom.Subscribe()
	defer subscription.Cancel()

	chatroom.Join(user)
	defer chatroom.Leave(user)

	// Send down the archive.
	for _, event := range subscription.Archive {
		if ws.MessageSendJSON(&event) != nil {
			// They disconnected
			return nil
		}
	}

	// In order to select between websocket messages and subscription events, we
	// need to stuff websocket events into a channel.
	newMessages := make(chan string)
	go func() {
		var msg string
		for {
			err := ws.MessageReceiveJSON(&msg)
			if err != nil {
				close(newMessages)
				return
			}
			newMessages <- msg
		}
	}()

	// Now listen for new events from either the websocket or the chatroom.
	for {
		select {
		case event := <-subscription.New:
			if ws.MessageSendJSON(&event) != nil {
				// They disconnected.
				return nil
			}
		case msg, ok := <-newMessages:
			// If the channel is closed, they disconnected.
			if !ok {
				return nil
			}

			// Otherwise, say something.
			chatroom.Say(user, msg)
		}
	}

	return nil
}

なるほど、なんとなく構成要素は分かったので、revelでやってみますか。

[Go言語] JSとGoでchatを作りたい その4

script.js

//
	document.getElementById("message").addEventListener("keydown", function(event){
		if (event.code === "Enter"){
			if(!socket){
				console.log("no connection")
				return false
			}
			event.preventDefault()
			event.stopPropagation()
			sendMessage()
		}
	})
//
function sendMessage(){
	console.log("Send Message...")
	let jsonData = {}
	jsonData["action"] = "broadcast"
	jsonData["username"] = document.getElementById("username").value
	jsonData["message"] = document.getElementById("message").value
	socket.send(JSON.stringify(jsonData))
	document.getElementById("message").value = ""
}

html

				<button id="submit" class="submit" onclick="sendMessage()">
					<i class="far far-paper-plane"></i>
				</button>

handler.go

func ListenToWsChannel(){
	var response domain.WsJsonResponse

	for {
		e := <-wsChan

		switch e.Action {
		case "username":
			clients[e.Conn] = e.Username
			users := getUserList()
			response.Action = "list_users"
			response.ConnectedUsers = users
			broadcastToAll(response)

		case "left":
			response.Action = "list_users"
			delete(clients, e.Conn)
			users := getUserList()
			response.ConnectedUsers = users
			broadcastToAll(response)
		case "broadcast":
			response.Action = "broadcast"
			response.Message = fmt.Sprintf(
				"<li class='replace'><strong>%s</strong>: %s</li>",
				e.Username,
				e.Message)
			broadcastToAll(response)
		}
	}
}

script.js

	socket.onmessage = msg => {
		let data = JSON.parse(msg.data)
		console.log({data})
		console.log("Action is", data.action)
		switch(data.action){
			case "list_users":
				let ul = document.getElementById("online-users")
				while (ul.firstChild) ul.removeChild(ul.firstChild)

				if (data.connected_users.length > 0){
					data.connected_users.forEach(function(item){
						let li = document.createElement("li")
						li.appendChild(document.createTextNode(item))
						ul.appendChild(li)
					})
				}
				break
			case "broadcast":
				let message = data.message
				let username = document.getElementById("username").value
				if (message.indexOf(username) > 0){
					message = message.replace("replace", "me")
				} else {
					message = message.replace("replace", "other")
				}
				messageList.innerHTML = messageList.innerHTML + message
				break
		}
	}

おおおおおおおおお、これは凄い
あとはrevelでやってfrontを書くか

[Go言語] JSとGoでchatを作りたい その3

usernameをserver側に送る

script.js

	let userInput = document.getElementById("username")
	userInput.addEventListener("change", function(){
		let jsonData = {}
		jsonData["action"] = "username"
		jsonData["username"] = this.value;

		socket.send(JSON.stringify(jsonData))
	})

handlers.go

func getUserList() []string {
	var clientList []string
	for _, client := range clients {
		if client != "" {
			clientList = append(clientList, client)
		}
	}
	sort.Strings(clientList)
	return clientList
}

connect.go

type WsJsonResponse struct {
	Action string `json:"action"`
	Message string `json:"message"`
	ConnectedUsers []string `json:"connected_users"`
}

script.js

	socket.onmessage = msg => {
		let data = JSON.parse(msg.data)
		console.log({data})
		console.log("Action is", data.action)
		switch(data.action){
			case "list_users":
				let ul = document.getElementById("online-users")
				while (ul.firstChild) ul.removeChild(ul.firstChild)

				if (data.connected_users.length > 0){
					data.connected_users.forEach(function(item){
						let li = document.createElement("li")
						li.appendChild(document.createTextNode(item))
						ul.appendChild(li)
					})
				}
				break
		}
	}

script.js

window.onbeforeunload = function(){
	console.log("User Leaving")
	let jsonData = {}
	jsonData["action"] = "left"
	socket.send(JSON.stringify(jsonData))
}
func ListenToWsChannel(){
	var response domain.WsJsonResponse

	for {
		e := <-wsChan

		switch e.Action {
		case "username":
			clients[e.Conn] = e.Username
			users := getUserList()
			response.Action = "list_users"
			response.ConnectedUsers = users
			broadcastToAll(response)

		case "left":
			response.Action = "list_users"
			delete(clients, e.Conn)
			users := getUserList()
			response.ConnectedUsers = users
			broadcastToAll(response)
		}
	}
}

ちょっとこんがらがってきました。

[Go言語] JSとGoでchatを作りたい その2

### Websocketのハンドリング
connection.go

package domain

import "github.com/gorilla/websocket"

type WsJsonResponse struct {
	Action string `json:"action"`
	Message string `json:"message"`
}

type WebSocketConnection struct {
	*websocket.Conn
}

type WsPlayload struct {
	Action String `json:"action"`
	Message string `json:"message"`
	Username string `json:"username"`
	Conn WebSocketConnection `json:"-"`
}

handlers.go

import (
	"chat/domain"
	"log"
	"net/http"
	"fmt"

	"github.com/CloudyKit/jet/v6"
	"github.com/gorilla/websocket"
)

var views = jet.NewSet(
	jet.NewOSFileSystemLoader("./html"),
	jet.InDevelopmentMode(),
)

var upgradeConnection = websocket.Upgrader {
	ReadBufferSize: 1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {return true},
}

var (
	wsChan = make(chan domain.WsPayload)

	clients = make(map[domain.WebSocketConnection]string)
)

func WsEndpoint(w http.ResponseWriter, r *http.Request){
	ws, err := upgradeConnection.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
	}
	log.Println("OK client connecting")

	conn := domain.WebSocketConnection{Conn: ws}
	clients[conn] = ""

	go ListenForWs(&conn)


	var response domain.WsJsonResponse
	response.Message = `<li>Connect to server</li>`

	err = ws.WriteJSON(response)
	if err != nil {
		log.Println(err)
	}
}

func Home(w http.ResponseWriter, r *http.Request){
	err := renderPage(w, "home.jet", nil)
	if err != nil {
		log.Println(err)
	}
}

func renderPage(w http.ResponseWriter, tmpl string, data jet.VarMap) error {
	view, err := views.GetTemplate(tmpl)
	if err != nil {
		log.Println(err)
		return err
	}

	err = view.Execute(w, data, nil)
	if err != nil {
		log.Println(err)
		return err
	}
	return nil
}

func ListenForWs(conn *domain.WebSocketConnection){
	defer func(){
		if r := recover(); r != nil {
			log.Println("Error", fmt.Sprintf("%v", r))
		}
	}()

	var payload domain.WsPayload

	for {
		err := conn.ReadJSON(&payload)
		if err != nil {

		} else {
			payload.Conn = *conn
			wsChan <- payload
		}
	}
}

func broadcastToAll(response domain.WsJsonResponse){
	for client := range clients {
		err := client.WriteJSON(response)
		if err != nil {
			log.Println("websockets err")
			_ = client.Close()
			delete(clients, client)
		}
	}
}

func ListenToWsChannel(){
	var response domain.WsJsonResponse

	for {
		e := <-wsChan

		response.Action = "Got here"
		response.Message = fmt.Sprintf("Some message, and action was %s", e.Action)

		broadcastToAll(response)
	}
}

goroutineを使って、ListenForWs関数を別プロセスで呼び出すことで、ブラウザからの通信を常にキャッチし続ける状態を作る。gorouteは go と書くだけ

main.go

import (
	"chat/internal/handlers"
	"log"
	"net/http"
)

func main(){
	mux := routes()
	log.Println("Starting channel listener")
	go handlers.ListenToWsChannel()

	log.Println("Starting web server on port 8080")

	_ = http.ListenAndServe(":8080", mux)
}

successfully connected
script.js:21 {action: ”, message: ‘

  • Connect to server
  • ‘}

    2021/10/18 03:41:26 Error runtime error: invalid memory address or nil pointer dereference
    2021/10/18 03:41:30 Error repeated read on failed websocket connection
    2021/10/18 03:41:30 OK client connecting

    なんか上手くいってるっぽいが、どういう仕組みなのか全然理解できない…

    [Go言語] JSとGoでchatを作りたい その1

    $ mkdir go
    $ cd go
    $ go mod init chat
    $ mkdir -p cmd/web
    $ mkdir -p internal/handlers
    $ mkdir html
    $ touch cmd/web/main.go
    $ touch cmd/web/routes.go
    $ touch html/home.jet
    $ touch internal/handlers/handlers.go
    $ mkdir domain
    $ touch domain/connect.go
    $ mkdir static
    $ touch static/scripts.js
    $ touch static/style.css

    ### モジュールの追加
    $ go get github.com/CloudyKit/jet/v6
    $ go get github.com/bmizerany/pat
    $ go get github.com/gorilla/websocket

    ### handler, route.go, main.go省略
    internal/handlers/hanlders.go
    cmd/web/main.go
    cmd/web/routes.go

    ### template
    home/home.jet

    <!DOCTYPE html>
    <html lang="en">
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    	<title>Document</title>
    	<link rel="stylesheet" href="/static/style.css">
    </head>
    <body>
    	<div class="chat-container">
    		<div class="chat-header">
    			<label for="username">Your Name</label>
    			<input type="text" id="username" class="username" autocomplete="off" placeholder=":) selfnote">
    		</div>
    		<div class="chat-body">
    			<ul id="message-list">
    				<li class="me">Sample</li>
    				<li class="other">Sample</li>
    			</ul>
    			<div class="send-area">
    				<input type="text" id="message" class="message" autocomplete="off" placeholder="message...">
    
    				<button id="submit" class="submit">
    					<i class="far far-paper-plane"></i>
    				</button>
    			</div>
    		</div>
    	</div>
    	<div class="oneline-user-container">
    		<ul id="online-users">
    			<li>XXXX</li>
    		</ul>
    	</div>
    	<script src="/static/scripts.js"></script>
    </body>
    </html>
    

    ### Websocketのエンドポイント作成
    /domain/connect.go
    L websocket返却用の構造体

    package domain
    
    type WsJsonResponse struct {
    	Action string `json:"action"`
    	Message string `json:"message"`
    }
    

    internal/handlers/hanlders.go

    import (
    	"chat/domain"
    	"log"
    	"net/http"
    
    	"github.com/CloudyKit/jet/v6"
    	"github.com/gorilla/websocket"
    )
    
    var upgradeConnection = websocket.Upgrader {
    	ReadBufferSize: 1024,
    	WriteBufferSize: 1024,
    	CheckOrigin: func(r *http.Request) bool {return true},
    }
    
    func WsEndpoint(w http.ResponseWriter, r *http.Request){
    	ws, err := upgradeConnection.Upgrade(w, r, nil)
    	if err != nil {
    		log.Println(err)
    	}
    	log.Println("OK client connecting")
    
    	var response domain.WsJsonResponse
    	response.Message = `<li>Connect to server</li>`
    
    	err = ws.WriteJSON(response)
    	if err != nil {
    		log.Println(err)
    	}
    }
    

    Route.go

    func routes() http.Handler {
    	mux := pat.New()
    
    	mux.Get("/", http.HandlerFunc(handlers.Home))
    	mux.Get("/ws", http.HandlerFunc(handlers.WsEndpoint)) // 追加
    
    	fileServer := http.FileServer(http.Dir("./static/"))
    	mux.Get("/static/", http.StripPrefix("/static", fileServer))
    
    	return mux
    }
    

    js

    let socket = null;
    
    document.addEventListener("DOMContentLoaded", function(){
    
    	socket = new WebSocket("ws://192.168.34.10:8080/ws")
    
    	socket.onopen = () => {
    		console.log("successfully connected")
    	}
    })
    

    console

    js

    document.addEventListener("DOMContentLoaded", function(){
    
    	socket = new WebSocket("ws://192.168.34.10:8080/ws")
    
    	socket.onopen = () => {
    		console.log("successfully connected")
    	}
    
    	socket.onclose = () => {
    		console.log("connection closed")
    	}
    
    	socket.onerror = error => {
    		console.log("there was an error")
    	}
    
    	socket.onmessage = msg => {
    		let j = JSON.parse(msg.data)
    		console.log(j)
    	}
    })
    

    うーむ、postされたらonloadというイメージなんだが…