I wanted to create some sort of administrative user interface for a backend messaging webserver. I really like Rust, but had a hard time finding a crate that could reliably render HTML while also providing session authentication and security. I've done some simple HTML templating with Go before, and it seemed like a good choice for this project. This page is dedicated to exploring how I created a frontend user interface allowing users to POST JSON content to a backend service.
<div class="container d-flex justify-content-center align-items-center min-vh-100">
<div class="card shadow" style="width: 100%; max-width: 400px;">
<div class="card-body">
<h2 class="card-title text-center mb-4">Login</h2>
{{if .Error}}
<div class="alert alert-danger">
{{.Error}}
</div>
{{end}}
<form action={{.LoginPath}} method="POST">
<input type="hidden" name="gorilla.csrf.Token" value="{{.CSRF}}">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
</form>
</div>
</div>
</div>
RunServer()
function, this HTML will be rendered by Go and served to the client as any static HTML
page.
renderLogin()
function defines how this will happen.
func (h *HandlerWithConfig) renderLogin(w http.ResponseWriter, r *http.Request, errorMsg string, csrfToken string) {
tmpl, err := template.ParseFiles("templates/login.html")
if err != nil {
log.Printf("Template error: %v", err)
http.Error(w, "Error loading template", http.StatusInternalServerError)
return
}
data := struct {
LoginPath string
Error string
CSRF string
}{
LoginPath: h.Config.LoginPath,
Error: errorMsg,
CSRF: csrfToken,
}
if err := tmpl.Execute(w, data); err != nil {
log.Printf("Template execution error: %v", err)
http.Error(w, "Error rendering template", http.StatusInternalServerError)
return
}
}
range .Languages
specifies that an array of Languages
will be used to generate a drop down displaying the name of each Language.
<div class="container mt-5">
<h2 class="mb-4">Send Koradi Bulletin</h2>
<form action="{{.SubmitPath}}" method="POST">
<!-- Add CSRF Token -->
<input type="hidden" name="gorilla.csrf.Token" value="{{.CSRF}}">
<div class="form-group">
<label for="language">Select Language:</label>
<select class="form-control" id="language" name="language">
{{range .Languages}}
<option value="{{.}}">{{.}}</option>
{{end}}
</select>
</div>
<div class="form-group">
<label for="duration">Select Duration:</label>
<select class="form-control" id="duration" name="duration">
{{range .Durations}}
<option value="{{.}}">{{.}}</option>
{{end}}
</select>
</div>
<div class="form-group">
<label for="title">Title:</label>
<input type="text" class="form-control" id="title" name="title" required>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea class="form-control" id="message" name="message" rows="4" required></textarea>
</div>
<div class="form-group">
<label for="image_url">Image URL (Optional):</label>
<input type="text" class="form-control" id="image_url" name="image_url">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
func (h *HandlerWithConfig) renderForm(w http.ResponseWriter, r *http.Request) {
data := MessageFormData{
Languages: []string{"English", "Spanish", "French", "Italian", "Portuguese", "German"},
Durations: []string{"Hour", "Day", "Week", "Quarter", "Year"},
SubmitPath: h.Config.SubmitMessagePath,
CSRF: csrf.Token(r),
}
tmpl, err := template.ParseFiles("templates/message.html")
if err != nil {
http.Error(w, "Error loading template", http.StatusInternalServerError)
return
}
tmpl.Execute(w, data)
}
The final step is to map endpoints to handler functions and run an HTTPS server. Server specifications can be defined to control request and response behavior, and additional middleware like logging and security headers can be layered on. Go has a lot of built in web functionality that makes this easy:
mux := http.NewServeMux()
mux.HandleFunc(cfg.Paths.Login, handler.loginHandler)
mux.Handle(cfg.Paths.Submit, handler.authMiddleware(http.HandlerFunc(handler.handleSubmit)))
mux.Handle(cfg.Paths.Create, handler.authMiddleware(http.HandlerFunc(handler.renderForm)))
server := &http.Server{
Addr: cfg.Server.Address,
Handler: middleware.Chain(mux, security, csrf, logging),
TLSConfig: tlsConfig,
ReadTimeout: cfg.Server.ReadTimeout,
WriteTimeout: cfg.Server.WriteTimeout,
IdleTimeout: cfg.Server.IdleTimeout,
}