gooey

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2019 License: BSD-2-Clause, Zlib Imports: 19 Imported by: 1

README

Gooey

A framework to create single executable web apps.

Overview

Gooey provides a base to create a single executable that displays a GUI through the user's default browser. It is designed to automatically open a browser tab when double clicked, communicate through websockets, and automatically shutdown when the user closes the last tab connected to the server. Some use cases for Gooey are simple tools that could benefit from a GUI or a debug dashboard for a service, all without the heavy weight of something like Electron or having try to use GUI bindings that are tied to a particular platform.

Quickstart

First, if you haven't already, go get github.com/0xABAD/gooey. Then create a new directory for a Go package and copy this code:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"time"

	"github.com/0xABAD/gooey"
)

func main() {
	var (
		app    testApp
		server gooey.Server
		notify = make(chan os.Signal)
		done   = make(chan struct{})
	)
	signal.Notify(notify, os.Kill, os.Interrupt)
	go func() {
		<-notify
		close(done)
	}()
	server.Start(done, &app)
}

type testApp struct{}

func (a *testApp) Start(closed <-chan struct{}, incoming <-chan []byte, outgoing chan<- interface{}) {
	count := 0
	ticker := time.NewTicker(1 * time.Second)
	for {
		select {
		case <-closed:
			return
		case <-ticker.C:
			outgoing <- fmt.Sprintf("Message from server.  Count %d", count)
			count++
		}
	}
}

Now run go run test.go. You should see something like this:

Gooey Demo

Some things you may have noticed:

  • A browser window was automatically opened
  • The address shows a random port number assigned
  • We see the message being pushed from the server once a second
  • The page loaded was embedded within the executable
  • When closing the tab the server was automatically shutdown

Of course, all of this functionality may be configured through the gooey.Server struct. See the documentation for more.

Setup Tool

Gooey comes with a setup tool that contains commands that may aid in development:

  • favicon go run setup.go favicon FILE PACKAGE_NAME: This will create a file named favicon.go that contains const FAVICON string where the value is the contents of FILE encoded in base64. The file will placed under the package designated by PACKAGE_NAME. The favicon.go will be created in the directory of where this command is run. You can assign the FAVICON string as a value to the FavIcon field in the gooey.Server struct.

LICENSE

Gooey is licensed under Zlib license but it does use gorilla/websocket, while having a permissive license, requires you redistibute its license whether redistibuting in source or binary form.

Documentation

Overview

Package gooey provides a web based GUI framework to create single executable web apps.

This package setups a server structure to produce an executable that launches a GUI app through the user's default web browser where the server and client communicate through a websocket. The intent is to embed the web content within the final executable in order to distribute a single executable to users that can double-click on the executable and start interacting with the program. The use case is for simple tools that can use a simple GUI or debug dashboards for server programs. It allows for simple GUI based tools written in Go without the heavy weight of an Electron app.

The following is an example of a minimalist gooey program:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"time"

	"github.com/0xABAD/gooey"
)

func main() {
	var (
		app    testApp
		server gooey.Server
		notify = make(chan os.Signal)
		done   = make(chan struct{})
	)
	signal.Notify(notify, os.Kill, os.Interrupt)
	go func() {
		<-notify
		close(done)
	}()
	server.Start(done, &app)
}

type testApp struct{}

func (a *testApp) Start(closed <-chan struct{}, incoming <-chan []byte, outgoing chan<- interface{}) {
	count := 0
	ticker := time.NewTicker(1 * time.Second)
	for {
		select {
		case <-closed:
			return
		case <-ticker.C:
			outgoing <- fmt.Sprintf("Message from server.  Count %d", count)
			count++
		}
	}
}

After running this program notice that the user doesn't have to navigate to the server's address in the web browser and that the tab opened automatically for them. Second, notice the port number is randomly assigned by the OS when gooey setups the server. Finally, see how the page updates when the messages from the server are sent to the client. This portrays gooey's client to server code that is embedded within the embedded index.html page.

One other point is the use of waiting for the os.Kill and os.Interrupt signals to cleanly shutdown the server. While this isn't necessary it ensures a proper clean up done by gooey to close current websocket connections and remove the temporary directory that it creates on server start. Also, note that by default the user doesn't need to stop the server by signaling an interrupt; instead, the user can simply close all open browser tabs connected to the server and the server will shut itself down.

Note that all what you seen here can be configured through the Server struct. Check the project's readme in its repository for a more in depth example that uses the various configuration options.

Index

Constants

View Source
const BROWSE = "xdg-open"
View Source
const FAVICON = "" /* 424-byte string literal not displayed */

Favicon with standard base64 encoding.

View Source
const INDEX = `` /* 2058-byte string literal not displayed */
View Source
const REDIRECT = `` /* 151-byte string literal not displayed */

Variables

This section is empty.

Functions

This section is empty.

Types

type App

type App interface {
	// Start is called by a gooey Server whenever a client connects to the server.  It
	// will be called once for each connecting client so any state to the connection
	// should be local to the function and any global state shared between all connections
	// should within App (or global).
	//
	// All messages to App are passed into the incoming channel and any messages to the
	// client should be passed on the outgoing channel.  The contents of byte slice passed
	// into the incoming channel are entirely up what's passed from the client as the
	// gooey Server passes the msg as is received.  Note that the gooey Server processes
	// all websocket messages as text messages.  The content passed to the outgoing
	// channel will be encoded as JSON before being sent on the websocket.  The message
	// on the client can be received by overriding the gooey.OnMessage function.  See
	// the documentation in gooey.js for more information on processing client side code.
	//
	// The closed channel will be closed when the connection with client has been closed
	// (i.e. the user closes the browser tab).  This allows to perform any clean up
	// that the App needs to perform.
	Start(closed <-chan struct{}, incoming <-chan []byte, outgoing chan<- interface{})
}

App provides a means sending and receiving websocket messages to connected clients.

type Server

type Server struct {
	// The IP address that the server should listen on.  It should be in the form of
	// "127.0.0.1:8080" such as accepted by the net and net/http packages.  If Addr
	// is the empty string then Server will listen on "127.0.0.1:" which will allow
	// the OS to select a random port for the connection.
	Addr string

	// The directory of where Server should serve web files from.  If WebServeDir is
	// the empty string then Server will serve files from a created temporary directory.
	// Server will generate index.html and favicon.ico files, dependent on the values
	// set for IndexHtml and FavIcon fields, and place them inside this temp directory
	// to be served.
	WebServeDir string

	// The index.html file that will be served to incoming client connections.  Note that
	// this is the contents of the index file, not the path to some index.html file.  It
	// it expected that these contents are embedded within the executable or loaded by
	// other means.  The contents of the string will be written to a temporary index.html
	// file and served to clients unless WebServeDir is not the empty string.  If this
	// field is the empty string then Server will use a default index.html file.
	IndexHtml string

	// The contents of the favicon.ico that is encoded in base64.  Like IndexHtml, these
	// are expected to be the actual favicon contents that will be written to temp file
	// to be served unless WebServeDir is set.  If this field is the empty string then
	// a default gooey favicon will be used.
	FavIcon string

	// Set ForceIndexAndFavIcon to true to have both custom (or default) IndexHtml/FavIcon
	// along with serving content from WebServeDir.  This combination of flags may come
	// across confusing so refer to the following to decide what is needed:
	//
	//     - Use the default index page and favicon that comes with Gooey and nothing else.
	//       * Set WebServeDir, IndexHtml, and FavIcon to the empty string.
	//       * Set ForceIndexAndFavIcon to false
	//     - Use a custom index page and/or a custom favicon.
	//       * Set IndexHtml and/or FavIcon to the desired strings.
	//       * Set WebServeDir to the empty string.
	//       * Set ForceIndexAndFavIcon to false.
	//     - Use a specified directory for all content.
	//       * Set WebServeDir to the specified directory.
	//       * Any value for IndexHtml and FavIcon (they're ignored now).
	//       * Set ForceIndexAndFavIcon to false.
	//     - Use a custom (or Gooey default) index page and/or favicon but also serve
	//       additional content from a specified directory.
	//       * Set IndexHtml and FavIcon to the desired values.
	//       * Set WebServeDir to the extra content directory path.
	//       * Set ForceIndexAndFavIcon to true.
	ForceIndexAndFavIcon bool

	// Specifies a directory whose contents will be watched (recursively) for changes and
	// when a change is detected then a special message will be sent to the client to
	// reload the page contents.  If this field is the empty string then no hot reloading
	// will occur.
	//
	// There are special rules for which files be sent for the hot reload.  First, if
	// one of the files that has changed is named body.html then the contents of that
	// file will replace the body of the current document loaded in the client.  If body.html
	// is removed then the body of the document will be replaced with an empty <div> tag.
	// If a CSS or a Javascript file is added or changed then all known CSS or Javascript
	// content will be concatenated together and replace a gooey specially constructed
	// <style> or <script> tag that is appended at the end of the document <head> element,
	// if it exist.  If a CSS or a Javascript file is removed then the contents of the
	// file will be omitted from the concatenated content.
	//
	// Javascript files have an additional rule that allows enforcement of ordering of
	// Javascript content that will be embedded in the document.  If a Javascript file has a
	// number after a dot in the file name but before the .js extenstion then that number
	// declares the ordering amongst other ordered Javascript files which will also be inserted
	// before other non ordered watched javascript files.  For example, suppose we have three
	// Javascript files: baz.js, bar.3.js, and foo.0.js, and suppose the current <head>
	// tag is as follows:
	//
	// <head>
	//   <script src="/somescript.js"></script>
	//   <link rel="stylesheet" href="/somestyle.css">
	// </head>
	//
	// Then the hot reload output will be:
	//
	// <head>
	//   <script src="/somescript.js"></script>
	//   <link rel="stylesheet" href="/somestyle.css">
	//   <script id="gooey-reload-js-content">
	//   /* concatenated content of foo.0.js, bar.3.js, and then baz.js */
	//   </script>
	// </head>
	ReloadWatchDir string

	// If ReloadWatchDir is not the empty then  gooey will ignore all files
	// that match any of these patterns.  That match algorithm used is the
	// same as specified in path.Match.
	ReloadIgnorePatterns []string

	// If this field is set to true then the server will not automatically shutdown after
	// the last client connection to the server is closed.
	NoAutoShutdown bool

	// If this field is set to true then the server will not open a browser tab in the
	// users default browser on server start.  It should be noted that if this field
	// is set to true then it might be wise to assign a custom address to the Addr
	// field so one knows how to connect to the server.
	NoAutoOpen bool

	// A logger for the server to post informational to, essentially enabling a verbose
	// mode.  If set to nil then no info messages will be posted.
	InfoLog *log.Logger

	// A logger for the server to post runtime error messages to.  If set to nil then
	// no error messages will be posted.  Note that the errors posted to this logger
	// are not the same that returned from the Start method, as those are initialization
	// errors and the server can not start.  These errors are runtime errors that are
	// not necessarily fatal to the server (e.g. a websocket WritMessage error).
	ErrorLog *log.Logger

	// A channel, if non nil, will be passed any runtime errors that are encountered
	// during server operation.  These errors are the exact same that are passed to
	// ErrorLog.  Note that these two fields are not mutually exclusive as any error
	// encountered will be written to the error log and this channel.
	ErrorC chan<- error
}

Server represents an active server connection that can listen to incoming connecting clients.

func (*Server) Start

func (server *Server) Start(done <-chan struct{}, app App) error

Start the server and allow incoming client connections. If an intialization error occurs then the start fails and that error is returned, otherwise, the server is started and this call blocks until there are no more clients connected (if NoAutoShutdown is false) or the done channel is closed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL