Skip to content

Commit

Permalink
Merge pull request #28 from fraunhoferfokus/development
Browse files Browse the repository at this point in the history
* add sse support especially for progress bar
  • Loading branch information
JGottschick authored May 20, 2024
2 parents 5d2a960 + 77b7ae5 commit 54fc728
Show file tree
Hide file tree
Showing 14 changed files with 585 additions and 20 deletions.
20 changes: 20 additions & 0 deletions Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ If an API endpoint contains a tag _builtin_ in your _tags_, then no handler code

If an API endpoint contains a tag _page_ in your _tags_, then a [templ](https://templ.guide/) template will also be created. A templ template allows to write HTML pages mixed with go code and generate a go function, which can be used easily in your handlers. Further, a localizer and a language selector (_languages.templ_) is setup to translate strings using a [i18n library](github.com/nicksnyder/go-i18n/v2/i18n) for internationalization.

### Server Side Events

If in the OpenAPI specification for the API endpoints the path "/events" with the builtin operations _get_ and _post_ are given, additional code for Server Side Events (_SSE_) (_rest/handleEvents.go_) will be generated. Especially, for tasks, which will need longer the functions _ProgressPico_ and _ProgressBootstrap_ (_rest/progress.go_) can be used to send using server side events a _progress bar_ code to HTMX for Pico and Bootstrap CSS, e.g.

f := func() {
_, err := http.Get("http://localhost:9090/slowz")
if err != nil {
log.Warn().Err(err).Msg("Slow call failed")
}
}
ProgressPico(f)

The _progress bar_ itself need to be declared in the frontend, e.g.

<script src="js/sse.js"></script>

<div hx-ext="sse" sse-connect="/events?stream=progress" sse-swap="Progress"></div>

and will be visible, when a call start, progress over time and reappear at the end.

### Configuration

The generated service can be configured using the default values, a _.env_ file, environment variables and the command line options (highest priority).
Expand Down
1 change: 1 addition & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ download-style:
curl -o templates/web/js/htmx.min.js -L https://unpkg.com/htmx.org@latest
curl -o templates/web/js/htmx-sse.js -L https://unpkg.com/htmx.org/dist/ext/sse.js
curl -o templates/web/js/hyperscript.js -L https://unpkg.com/hyperscript.org@latest
curl -o templates/web/js/sse.js -L https://unpkg.com/htmx.org/dist/ext/sse.js
curl -o templates/web/css/simple.min.css -L https://unpkg.com/simpledotcss/simple.min.css
curl -o templates/web/css/pico.min.css -L https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css
curl -o templates/web/css/pico.colors.min.css -L https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css
Expand Down
2 changes: 1 addition & 1 deletion core/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.17
0.2.18
23 changes: 23 additions & 0 deletions examples/OpenAPI.yaml.min-example
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,29 @@ paths:
application/json:
schema:
type: string
/events:
get:
summary: SSE events service
tags:
- builtin
responses:
"200":
description: SSE event communication
content:
text/plain:
schema:
type: string
post:
summary: SSE events service
tags:
- builtin
responses:
"200":
description: SSE event communication
content:
text/plain:
schema:
type: string
/robots.txt:
get:
summary: return robots.txt to restrict web crawlers
Expand Down
7 changes: 1 addition & 6 deletions generator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,10 @@ func generateConfigFiles(serverConf ServerConfig) {
fileName = "version"
filePath = filepath.Join(config.Path, CorePkg, fileName)
templateFile = "templates/core/version"
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
if _, err := os.Stat(fileName); errors.Is(err, os.ErrNotExist) {
createFileFromTemplate(filePath, templateFile, serverConf)
if err = os.Symlink(filePath, fileName); err != nil {
log.Warn().Err(err).Str("source", filePath).Str("target", fileName).Msg("Could not create symbolic Link, please create it manually")
}
// if runtime.GOOS == "windows" {
// extCmd.RunCommand("mklink /h "+fileName+" "+filePath, config.Path)
// } else {
// extCmd.RunCommand("ln -s "+filePath+" "+fileName, config.Path)
// }
}
}
17 changes: 16 additions & 1 deletion generator/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package generator
import (
fs "dredger/fileUtils"
"errors"
"net/http"
"os"
"path/filepath"
"reflect"
"slices"
"strconv"
"strings"

Expand All @@ -18,9 +20,20 @@ func generateEmptyFrontend(_ *openapi3.T, conf GeneratorConfig) {
createFileFromTemplate(filepath.Join(frontendPath, "README.md"), "templates/web/README.md.tmpl", nil)
}

func generateFrontend(_ *openapi3.T, conf GeneratorConfig) {
func generateFrontend(spec *openapi3.T, conf GeneratorConfig) {
generateOpenAPIDoc(conf)

if spec.Paths.Find("/events") == nil || (spec.Paths.Find("/events").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/events").Operations()[http.MethodGet].Tags, "builtin")) {
log.Debug().Msg("Generating default /events endpoint.")

op := openapi3.NewOperation()
op.AddResponse(200, createOAPIResponse("The service supports sse"))
op.AddResponse(http.StatusOK, createOAPIResponse("The service don't support sse"))
updateOAPIOperation(op, "HandleEvents", "support for sse", "200")
spec.AddOperation("/events", http.MethodGet, op)
spec.AddOperation("/events", http.MethodPost, op)
}

// create folders
restPath := filepath.Join(conf.OutputPath, "rest")
frontendPath := filepath.Join(conf.OutputPath, "web")
Expand All @@ -46,6 +59,7 @@ func generateFrontend(_ *openapi3.T, conf GeneratorConfig) {
fs.CopyWebFile("web/js", javascriptPath, "htmx.min.js", true)
fs.CopyWebFile("web/js", javascriptPath, "hyperscript.js", true)
fs.CopyWebFile("web/js", javascriptPath, "rapidoc-min.js", true)
fs.CopyWebFile("web/js", javascriptPath, "sse.js", true)

// files in stylesheet directory
fs.CopyWebFile("web/css", stylesheetPath, "bootstrap-icons.min.css", true)
Expand All @@ -59,6 +73,7 @@ func generateFrontend(_ *openapi3.T, conf GeneratorConfig) {

// files in pages directory
fs.CopyWebFile("web/pages", restPath, "render.go", true)
fs.CopyWebFile("web/pages", restPath, "progress.go", true)
createFileFromTemplate(filepath.Join(pagesPath, "localize.go"), "templates/web/pages/localize.go.tmpl", conf)
if _, err := os.Stat(filepath.Join(pagesPath, "languages.templ")); errors.Is(err, os.ErrNotExist) {
createFileFromTemplate(filepath.Join(pagesPath, "languages.templ"), "templates/web/pages/languages.templ.tmpl", conf)
Expand Down
3 changes: 3 additions & 0 deletions generator/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ func generateHandlerFuncStub(op *openapi3.Operation, method string, path string,
if conf.OperationID == "GetRobots" {
templateFile = "templates/rest/getRobots.go.tmpl"
}
if conf.OperationID == "HandleEvents" {
templateFile = "templates/rest/sseEvents.go.tmpl"
}

if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
createFileFromTemplate(filePath, templateFile, conf)
Expand Down
2 changes: 1 addition & 1 deletion templates/core/config.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Config struct {
Tracing bool `default:"false"`
Language string `default:"de"`
Languages []string `default:"en,de"`
JaegerCollector string `default:"" split_words:"true"`
UseSse bool `default:"false"`
ConfigExt
}

Expand Down
15 changes: 5 additions & 10 deletions templates/core/tracing/tracing.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,12 @@ func InitTracer() (*sdktrace.TracerProvider, error) {
var exporter sdktrace.SpanExporter
var err error
// output traces locally on stdout
if core.AppConfig.JaegerCollector != "" {
// Jaeger exporter no longer supported
log.Warn().Msg("Jaeger tracing exporter no longer supported")
} else {
exporter, err = stdout.New(stdout.WithPrettyPrint())
if err != nil {
log.Error().Err(err).Msg("creating stdout tracing exporter failed")
return nil, err
}
log.Info().Msg("created stdout tracing exporter")
exporter, err = stdout.New(stdout.WithPrettyPrint())
if err != nil {
log.Error().Err(err).Msg("creating stdout tracing exporter failed")
return nil, err
}
log.Info().Msg("created stdout tracing exporter")

tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.AlwaysSample())),
Expand Down
2 changes: 1 addition & 1 deletion templates/rest/handlerFunc.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func {{ .OperationID }}(c echo.Context) error {
{{ end }}
{{- end }}

// implement your functionality best using a function from a separate file, e.g. {{ .OperationID }}Do.go
// implement your functionality best using a function from a separate file, e.g. usecases/{{ .OperationID }}Do.go

{{ range .Responses }}
// {{ .StatusCode }} => {{ .Description }}
Expand Down
16 changes: 16 additions & 0 deletions templates/rest/sseEvents.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Don't edit this file, as it is generated by dredger
package rest

import (
"github.com/labstack/echo/v4"
"github.com/r3labs/sse/v2"
)

var SseServer *sse.Server
var HandleEvents echo.HandlerFunc

func init() {
SseServer = sse.New()
SseServer.CreateStream("progress")
HandleEvents = echo.WrapHandler(SseServer)
}
5 changes: 5 additions & 0 deletions templates/web/README.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ This directory contains CSS stylesheets and Javascript libraries, which can be u

To include them in your web page use the local paths, e.g. _/css/pico.min.css_.


if useSSE {
<script src="js/sse.js"></script>
}

Further, the documentation of the OpenAPI specification can be viewed with rapidoc (see folder _doc_).

For all operations in the OpenAPI specification, which includes the tag _page_ a page template will be generated in the subdirectory _pages_.
Expand Down
Loading

0 comments on commit 54fc728

Please sign in to comment.