oderwat / go-nats-app Goto Github PK
View Code? Open in Web Editor NEWA Go code only PWA as proof of concept for using (embedded) NATS as communication between front and back end.
License: MIT License
A Go code only PWA as proof of concept for using (embedded) NATS as communication between front and back end.
License: MIT License
The 1.21.0 version is out.
would allow NATS TLS to work ?
I think about documenting our services with JSON Schema as part of the service code itself.
The NATS Developers do. Try:
nats schema search
nats schema info io.nats.jetstream.api.v1.account_info_response | jq .
What is it | URL | Comment |
---|---|---|
jsm.go a library to manage and interact with JetStream | https://github.com/nats-io/jsm.go | Contains a validator and their registry code |
JSON Schema from Go Struct | https://github.com/invopop/jsonschema | |
JSON Schema Validator | https://github.com/xeipuuv/gojsonschema | |
Yaml marshalling and unmarshalling | https://github.com/invopop/yaml | To convert between Yaml and JSON |
Hi @oderwat, I'm experiencing a very weird problem that need your help.
Please take a look at this minimum change that I made to the code:
suntong@44d30b9
IE, for the lines in
https://github.com/suntong/go-nats-app/blob/master/front/front.go#L141-L146
app.Range(uc.messages).Slice(func(i int) app.UI {
//return app.Div().Text(uc.messages[len(uc.messages)-1-i])
id := len(uc.messages) - 1 - i
app.Logf("chat[%d]: %s\n", id, uc.messages[id])
return &CodeBlock{code: uc.messages[id], id: fmt.Sprintf("chat%02d", id)}
}),
If I uncomment the return
statement, then everything is good.
Commenting it out, I'll get something like this:
IE, it is repeating only the 1st message, while the app.Logf
shows that the CodeBlock
assignment is done right.
What could possible be happened? It's almost looks like the well known "Referencing a loop variable later" problem (here, the top one), however, the same code works fine in my other example:
https://github.com/suntong/go-app-demos/blob/master/0B2A-codecopy/main.go#L47-L51
They both looks exactly the same to me. What makes the huge difference? I think it is more an architect problem that only you can find the answer. please help. thx.
@oderwat ,
May I request guidance and/or advice getting the secure websocket (wss://) connection to work with the embedded nats server. Below is my attempt. I tried numerous attempts but to no avail. I am missing something major in my understanding in my very limited knowledge of TLS certs. I do know they certs work - tested with an other go app (gorilla websockets). My ISP enabled 80/433/3000/4000 ports.
Thank you in advance.
package back
import (
"bytes"
"compress/flate"
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"image"
"image/jpeg"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/maxence-charriere/go-app/v9/pkg/app"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
"github.com/o1egl/govatar"
"golang.org/x/net/netutil"
)
// AppServer implements the Backend service
type AppServer struct {
mux *chi.Mux
}
func (srv *AppServer) Mux() *chi.Mux {
return srv.mux
}
type Muxer interface {
Mux() *chi.Mux
}
func Create(ah *app.Handler) {
var srv AppServer
cert, err := tls.LoadX509KeyPair(
"mysite.com/combined_certificate.pem",
"mysite.com/certificate-key.pem",
)
if err != nil {
log.Fatal(err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: "mysite.com",
InsecureSkipVerify: true,
}
opts := &server.Options{
ServerName: "Your friendly backend",
Host: "127.0.0.1",
Port: 8501,
NoLog: true,
NoSigs: true,
MaxControlLine: 4096,
Accounts: []*server.Account{
{
Name: "cluster",
},
},
Websocket: server.WebsocketOpts{
Host: "127.0.0.1",
Port: 3000,
NoTLS: false,
TLSConfig: tlsConfig,
SameOrigin: true,
AllowedOrigins: []string{
"wss://mysite.com",
"wss://mysite.com:3000",
"wss://www.mysite.com",
"wss://www.mysite.com:3000",
"https://mysite.com",
"https://mysite.com:3000",
"https://www.mysite.com",
"https://www.mysite.com:3000",
},
Compression: false,
HandshakeTimeout: 5 * time.Second,
},
DisableShortFirstPing: true,
}
// Initialize new server with options
ns, err := server.NewServer(opts)
if err != nil {
panic(err)
}
// Start the server via goroutine
go ns.Start()
// Wait for server to be ready for connections
if !ns.ReadyForConnections(2 * time.Second) {
panic("could not start the server (is another instance running on the same port?)")
}
fmt.Printf("Nats AppServer Name: %q\n", ns.Name())
fmt.Printf("Nats AppServer Addr: %q\n", ns.Addr())
// Connect to server
nc, err := nats.Connect(ns.ClientURL())
if err != nil {
panic(err)
}
// This is the chat broker
_, err = nc.Subscribe("chat.say", func(msg *nats.Msg) {
fmt.Printf("Got: %q\n", msg.Data)
err = nc.Publish("chat.room", msg.Data)
if err != nil {
panic(err)
}
})
if err != nil {
panic(err)
}
_, err = nc.Subscribe("govatar.female", func(msg *nats.Msg) {
if err != nil {
panic(err)
}
var img image.Image
// always female and random
img, err = govatar.Generate(govatar.FEMALE)
if err != nil {
panic(err)
}
var buf bytes.Buffer
err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 80})
if err != nil {
panic(err)
}
err = msg.Respond([]byte("data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())))
if err != nil {
log.Printf("Respond error: %s", err)
}
// noErr(err)
})
if err != nil {
panic(err)
}
r := chi.NewRouter()
srv.mux = r
r.Use(middleware.CleanPath)
r.Use(middleware.Logger)
compressor := middleware.NewCompressor(flate.DefaultCompression,
"application/wasm", "text/css", "image/svg+xml" /*, "application/javascript"*/)
r.Use(compressor.Handler)
r.Use(middleware.Recoverer)
listener, err := net.Listen("tcp", ":443")
if err != nil {
panic(err)
}
mainServer := &http.Server{
Addr: listener.Addr().String(),
TLSConfig: tlsConfig,
}
// try to build the final browser location
hostURL := url.URL{
Scheme: "https",
Host: mainServer.Addr,
}
log.Printf("Serving at %s\n", hostURL.String())
// This is the frontend handler (go-app) and will "pick up" any route
// which is not handled by the backend. It then will load the frontend
// and navigate it to this router.
srv.mux.Handle("/*", ah)
// registering our stack as the handler for the http server
mainServer.Handler = srv.Mux()
// Creating some graceful shutdown system
shutdown := make(chan struct{})
go func() {
listener = netutil.LimitListener(listener, 100)
defer func() { _ = listener.Close() }()
err := mainServer.ServeTLS(listener, "", "")
if err != nil {
if err == http.ErrServerClosed {
fmt.Println("AppServer was stopped!")
} else {
log.Fatal(err)
}
}
// when run by "mage watch/run" it will break mage
// before actually exiting the server, which just looks
// strange but is okay after all
// time.Sleep(2 * time.Second) // just for testing
close(shutdown)
}()
// Setting up signal capturing
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
// Waiting for SIGINT (kill -SIGINT <pid> or Ctrl+c )
<-stop
shutdownServer(mainServer, shutdown)
fmt.Println("Program ended")
}
// shutdownServer stops the server gracefully
func shutdownServer(server http.Server, shutdown chan struct{}) {
fmt.Println("\nStopping AppServer gracefully")
ctx, cancel := context.WithTimeout(context.Background(), 5time.Second)
defer cancel()
fmt.Println("Send shutdown!")
if err := server.Shutdown(ctx); err != nil {
log.Fatal(err)
}
fmt.Println("Waiting for server to shutdown!")
<-shutdown
fmt.Println("AppServer is shutdown!")
}
How would you like to facilitate the option of running with and without the embedded NATS Server ?
This is simple commented out code to allow to use an external. Seems to work in testing.
func Create(ah *app.Handler) {
var srv AppServer
/*
opts := &server.Options{
ServerName: "Your friendly backend",
Host: "127.0.0.1",
Port: 8501,
NoLog: true,
NoSigs: true,
MaxControlLine: 4096,
Accounts: []*server.Account{
{
Name: "cluster",
},
},
Websocket: server.WebsocketOpts{
Host: "127.0.0.1",
Port: 8502,
NoTLS: true,
SameOrigin: false,
Compression: false,
HandshakeTimeout: 5 * time.Second,
},
DisableShortFirstPing: true,
}
// Initialize new server with options
ns, err := server.NewServer(opts)
if err != nil {
panic(err)
}
// Start the server via goroutine
go ns.Start()
// Wait for server to be ready for connections
if !ns.ReadyForConnections(2 * time.Second) {
panic("could not start the server (is another instance running on the same port?)")
}
fmt.Printf("Nats AppServer Name: %q\n", ns.Name())
fmt.Printf("Nats AppServer Addr: %q\n", ns.Addr())
// Connect to server
nc, err := nats.Connect(ns.ClientURL())
if err != nil {
panic(err)
}
*/
// ws://localhost:8080
nc, err := nats.Connect("ws://localhost:8502")
if err != nil {
panic(err)
}
// This is the chat broker
_, err = nc.Subscribe("chat.say", func(msg *nats.Msg) {
fmt.Printf("Got: %q\n", msg.Data)
err = nc.Publish("chat.room", msg.Data)
if err != nil {
panic(err)
}
})
if err != nil {
panic(err)
}
_, err = nc.Subscribe("govatar.female", func(msg *nats.Msg) {
if err != nil {
panic(err)
}
var img image.Image
// always female and random
img, err = govatar.Generate(govatar.FEMALE)
if err != nil {
panic(err)
}
var buf bytes.Buffer
err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 80})
if err != nil {
panic(err)
}
err = msg.Respond([]byte("data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())))
if err != nil {
log.Printf("Respond error: %s", err)
}
//noErr(err)
})
if err != nil {
panic(err)
}
r := chi.NewRouter()
srv.mux = r
r.Use(middleware.CleanPath)
r.Use(middleware.Logger)
compressor := middleware.NewCompressor(flate.DefaultCompression,
"application/wasm", "text/css", "image/svg+xml" /*, "application/javascript"*/)
r.Use(compressor.Handler)
r.Use(middleware.Recoverer)
listener, err := net.Listen("tcp", "127.0.0.1:8500")
if err != nil {
panic(err)
}
mainServer := &http.Server{
Addr: listener.Addr().String(),
}
// try to build the final browser location
var hostURL = url.URL{
Scheme: "http",
Host: mainServer.Addr,
}
log.Printf("Serving at %s\n", hostURL.String())
// This is the frontend handler (go-app) and will "pick up" any route
// which is not handled by the backend. It then will load the frontend
// and navigate it to this router.
srv.mux.Handle("/*", ah)
// registering our stack as the handler for the http server
mainServer.Handler = srv.Mux()
// Creating some graceful shutdown system
shutdown := make(chan struct{})
go func() {
listener = netutil.LimitListener(listener, 100)
defer func() { _ = listener.Close() }()
err := mainServer.Serve(listener)
if err != nil {
if err == http.ErrServerClosed {
fmt.Println("AppServer was stopped!")
} else {
log.Fatal(err)
}
}
// when run by "mage watch/run" it will break mage
// before actually exiting the server, which just looks
// strange but is okay after all
//time.Sleep(2 * time.Second) // just for testing
close(shutdown)
}()
// Setting up signal capturing
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
// Waiting for SIGINT (kill -SIGINT <pid> or Ctrl+c )
<-stop
shutdownServer(mainServer, shutdown)
fmt.Println("Program ended")
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.