Coder Social home page Coder Social logo

go-nats-app's People

Contributors

oderwat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

go-nats-app's Issues

Some useful Go packages that are related to using NATS for PWAs

Some useful packages

JSON Schema

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

Please assist with TLS config

@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(), 5
time.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!")
}

Running with and without the embedded NATS Server

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 very weird problem

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:

image

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.