Use root URL when using templates - go

I feel like this a simple problem, but I'm an utter noob and I cant seem to find an answer.
I'm using the following to present a specific html template based on the URL path
func handleTemplate(w http.ResponseWriter, r *http.Request) {
templates := populateTemplates()
requestedFile := r.URL.Path[1:]
t := templates.Lookup(requestedFile + ".html")
if t != nil {
err := t.Execute(w, nil)
if err != nil {
log.Println(err)
}
} else {
w.WriteHeader(http.StatusNotFound)
}
}
func main() {
http.HandleFunc("/", handleTemplate)
http.Handle("/img/", http.FileServer(http.Dir("static")))
http.Handle("/css/", http.FileServer(http.Dir("static")))
// Dev port binding
bind := fmt.Sprintf("%s:%s", "0.0.0.0", "5000")
fmt.Printf("listening on %s...", bind)
err := http.ListenAndServe(bind, nil)
if err != nil {
panic(err)
}
}
So that works great if I have a template called home.html.. I can browse to localhost/home and it will present me the right template.
However I want the user to browse to localhost/ and be presented with a specific html template. Is that possible without a framework?

Sure:
if r.URL.Path == "/" {
// Render default/index/whatever page
}

Related

Is there a good way to do cache saves in goroutine?

Let's say I have a handler that makes a request and gets the latest data on the selected stock:
func (ss *stockService) GetStockInfo(ctx *gin.Context) {
code := ctx.Param("symbol")
ss.logger.Info("code", code)
url := fmt.Sprintf("URL/%v", code)
ss.logger.Info(url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
defer resp.Body.Close()
var chart ChartResponse
err = json.NewDecoder(resp.Body).Decode(&chart)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
ctx.JSON(http.StatusOK, chart)
}
And I want to add caching here. Since I don't have a lot of experience right now, I'm interested in proper interaction with the cache.
I think that if, for example, it is not possible to save to the cache for some reason, then you can simply make a request to the api. Then I wonder if it would be right to save to the cache in a separate goroutine and immediately return the response:
func (ss *stockService) GetStockInfo(ctx *gin.Context) {
code := ctx.Param("symbol")
stockInfo, err := ss.cache.Get(code)
if err == nil {
// FIND
...
ctx.JSON(http.StatusOK, chart)
} else {
ss.logger.Info("code", code)
url := fmt.Sprintf("URL/%v", code)
ss.logger.Info(url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
...
err = json.NewDecoder(resp.Body).Decode(&chart)
// IS IT A GOOD WAY ?
go ss.cache.Save(code,chart,expireAt)
ctx.JSON(http.StatusOK, chart)
}
}
I use redis as a cache.
I will be glad if someone says what is wrong with this approach.

Get cookie from URL

I am wondering if there is a way to get a cookie from a URL using golang? I have tried to use a few examples that come up when I google it but it never seems to return the cookie
This seems to do it:
package http
import "net/http"
func get_cookie(ref, name string) (*http.Cookie, error) {
res, err := http.Get(ref)
if err != nil {
return nil, err
}
defer res.Body.Close()
for _, cook := range res.Cookies() {
if cook.Name == name {
return cook, nil
}
}
return nil, http.ErrNoCookie
}

how to use the output of a bash script in a Golang function

This might not even be possible but I've been researching for the past hour and come up blank. I have a bash script that gives me a certain output and I want to add that output to Golang in order to redirect a website based on the output of the bash script. Sorry if this makes no sense, im new to Go
Here is what I have to run the bash script and output the value
func main() {
out, err := exec.Command("/bin/sh", "script.sh").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf(string(out))
}
I then want to use the value that was output there in another function and to redirect a URL heres how I would redirect to a url and I wanted the $variable to be added. This is just an example I copied off the internet but its what I want to replicate.
func redirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "**$variable**", 301)
}
func main() {
http.HandleFunc("/", redirect)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
Assuming your script must be only run once at startup, then this should do the trick:
var redirectTo string
func redirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, redirectTo, 301)
}
func main() {
out, err := exec.Command("/bin/sh", "script.sh").Output()
if err != nil {
log.Fatal(err)
}
redirectTo = string(out)
http.HandleFunc("/", redirect)
err = http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
Or if you don't want to have a global variable you can generate the redirect function at runtime:
func main() {
out, err := exec.Command("/bin/sh", "script.sh").Output()
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, string(out), 301)
})
err = http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
if you want to handle when out is empty, you can do:
func redirect(w http.ResponseWriter, r *http.Request) {
if len(redirectTo) == 0 {
http.Error(w, "No URL parsed!", 500)
return
}
http.Redirect(w, r, redirectTo, 301)
}
This will return an HTTP error 500 in that case.
You can also simply exit the program instead of serving:
out, err := exec.Command("/bin/sh", "script.sh").Output()
if err != nil {
log.Fatal(err)
}
if len(out) == 0 {
log.Fatal("no output was returned from script")
}
You can also add more verifications to out here if you wish, like if it is a correct URL, using net/url package for instance.

How to Add Context to an Image, that is served via a Go Handler?

i have a Website managed in Go. On one of them, i serve QR-Codes, which can be used to Login to the Page.
I added the QR-Codes to the HTML-File like this:
<img alt="Failed to load QR-Code" src="/qr.png/{{.Loc}}">
and added the Handler as follows:
http.HandleFunc("/qr.png/", qrWrapper(checkLocation, checkPortAndPrintError, qrcodeHandler))
The Problem is, a different QR-Code for each supported Location needs to be generated. Currently i put it on the Website directly via the ExecuteTemplate-Function.
The Page the QR-Code is on, has the Location in the URL:
/token/Würzburg
As Example for the Location "Würzburg".
The Problem is, the User can just read out the HTML-Body in his Browser and access the Image directly.
Is it somehow possible, to serve the Image in the <img>-Tag, without having to actually write the Location in it? Bypassing the actual Site can be Security-Risk. Maybe you can somehow add Context to the Request? My qrWrapper cuts out the Location in the GET-URL. Is there a better solution? Maybe add a Filter in the Handler, so just the Webpage can access it, without putting it in the Website?
I hope its clear what i want to achive.
Below are all 3 Handlers:
func qrcodePageHandler(w http.ResponseWriter, r *http.Request) {
loc, ok := r.Context().Value("loc").(authentification.Location)
if !ok {
http.Error(w, fmt.Sprintf("Error while creating qrcode-Page: Failed to load Location"), http.StatusBadRequest)
return
}
err := executeTemplateFunc("qr", qrPage, w, qrPageContent{
Loc: loc.Name,
Timer: RefreshTimer,
})
if err != nil {
http.Error(w, fmt.Sprintf("Error while creating qrcode-Page: %v", err), http.StatusInternalServerError)
return
}
}
func qrWrapper(lf locationCheckerFunc, cf portCheckerFunc, handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
loc := path.Base(r.URL.Path)
locStruct, ok := lf.authenticate(loc)
if strings.Contains(loc, "token") || !ok {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
} else {
if !cf(w, *r, QrPort, http.StatusForbidden) {
return
}
ctx := context.WithValue(r.Context(), "loc", locStruct)
handler(w, r.WithContext(ctx))
}
}
}
func qrcodeHandler(w http.ResponseWriter, r *http.Request) {
loc, ok := r.Context().Value("loc").(authentification.Location)
if !ok {
http.Error(w, fmt.Sprintf("Error while creating qrcode: Failed to load Location"), http.StatusBadRequest)
return
}
_, qrcode, err := qrCodes.QRCode(loc, fmt.Sprintf("%s%s", SrvUrl, "?token="), qrRecoveryLevel, 400)
if err != nil {
http.Error(w, fmt.Sprintf("Error while creating qrcode: %v", err), http.StatusInternalServerError)
return
}
_, err = w.Write(qrcode)
if err != nil {
http.Error(w, fmt.Sprintf("Error while showing qrcode: %v", err), http.StatusInternalServerError)
return
}
}

How to serve file from go embed

I have a static directory, containing a sign.html file :
//go:embed static
var static embed.FS
It is served that way and works fine :
fSys, err := fs.Sub(static, "static")
if err != nil {
return err
}
mux.Handle("/", http.FileServer(http.FS(fSys)))
On some routes though (for instance: /sign), I want to do some checks before serving the page. This is my handler :
func (h Handler) ServeSignPage(w http.ResponseWriter, r *http.Request) error {
publicKey := r.URL.Query().Get("publicKey")
err := h.Service.AuthorizeClientSigning(r.Context(), publicKey)
if err != nil {
return err
}
// this is where I'd like to serve the embed file
// sign.html from the static directory
http.ServeFile(w, r, "sign.html")
return nil
}
Unfortunately, the ServeFile displays not found. How can I serve the file from the file server within that ServeSignPage ?
Option 1
Read the file to a slice of bytes. Write the bytes to the response.
p, err := static.ReadFile("static/sign.html")
if err != nil {
// TODO: Handle error as appropriate for the application.
}
w.Write(p)
Option 2
If the path for the ServeSignPage handler is the same as the static file in the file server, then delegate to the file server.
Store the file server in a package-level variable.
var staticServer http.Handler
func init() {
fSys, err := fs.Sub(static, "static")
if err != nil {
panic(err)
}
staticServer = http.FileServer(http.FS(fSys)))
}
Use the static server as the handler:
mux.Handle("/", staticServer)
Delegate to the static server in ServeSignPage:
func (h Handler) ServeSignPage(w http.ResponseWriter, r *http.Request) error {
publicKey := r.URL.Query().Get("publicKey")
err := h.Service.AuthorizeClientSigning(r.Context(), publicKey)
if err != nil {
return err
}
staticServer.ServeHTTP(w, r)
return nil
}

Resources