Golang web app localization - http

I have a web app written in golang, and I am planning to make it available in more than one language, I've taken a look at multiple available l18n packages but some things were not clear to me.
What packages would be ideal to determine the users locale and load the site accordingly? Like from browser preferences or location?

You can use https://github.com/nicksnyder/go-i18n/
Then in your project you have to create a folder called i18n/ and use a function like this:
import (
"fmt"
"io/ioutil"
"github.com/nicksnyder/go-i18n/i18n"
)
func loadI18nFiles() {
files, _ := ioutil.ReadDir("i18n")
exists := false
for _, file := range files {
if err := i18n.LoadTranslationFile(fmt.Sprintf("i18n/%s", file.Name())); err != nil {
log.Errorf("i18n: error loading file %s. err: %s", file.Name(), err)
} else {
log.Infof("i18n: lang file %s loaded", file.Name())
}
# Check if you have a default language
if file.Name() == fmt.Sprintf("%s.json", "en-US") {
exists = true
}
}
if !exists {
panic(fmt.Sprintf("Hey! You can't use a default language (%s) that doesn't exists on i18n folder", props.DefaultLang))
}
}
Then to use, import the package and call the function:
T, _ := i18n.Tfunc("es-AR", "en-US")
fmt.Printf(T("key"))
Each file inside i18n folder is a .json
Example:
en-US.json
[
{
"id": "key",
"translation": "Hello World"
}
]
es-AR.json
[
{
"id": "key",
"translation": "Hola Mundo"
}
]

Related

Pull info from private site that requires login (Golang)

I am trying to pull my classes from my online timetable, however, it seems as if I cannot get past the login stage. My code is:
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"github.com/PuerkitoBio/goquery"
)
type App struct {
Client *http.Client
}
type Timetable struct {
Name string
}
const (
baseURL string = "https://myclasswebsite.com"
)
func (app *App) login() {
//login := loginInfo()
client := app.Client
loginURL := baseURL + "/portal2/#!/login"
data := url.Values{
"inputEmail": {"my_actual_username"},
"password": {"my_actual_password"},
}
response, err := client.PostForm(loginURL, data)
if err != nil {
log.Fatalln(err)
}
defer response.Body.Close()
_, err = ioutil.ReadAll(response.Body)
if err != nil {
log.Fatalln(err)
}
}
func (app *App) getTimetable() []Timetable {
timetableURL := baseURL + "/portal/dashboard"
client := app.Client
response, err := client.Get(timetableURL)
if err != nil {
log.Fatalln("Error fetching response. ", err)
}
defer response.Body.Close()
document, err := goquery.NewDocumentFromReader(response.Body)
fmt.Println(document.Html())
if err != nil {
log.Fatal("Error loading HTTP response body. ", err)
}
var classes []Timetable
document.Find(".timetable table").Each(func(i int, s *goquery.Selection) {
className := strings.TrimSpace(s.Text())
class := Timetable{
Name: className,
}
classes = append(classes, class)
})
return classes
}
I changed the base URL and login info just for privacy reasons, however, the rest of the code is as-is.
My main. go file is:
package main
import (
"fmt"
"net/http"
"net/http/cookiejar"
)
func main() {
jar, _ := cookiejar.New(nil)
app := App{
Client: &http.Client{Jar: jar},
}
app.login()
classes := app.getTimetable()
fmt.Println("class array is", classes)
for index, class := range classes {
fmt.Printf("%d: %s\n", index+1, class.Name)
}
}
The final print returns an empty slice, and when I print the response.Html() to the console, I receive the login-page Html rather than the dashboard-page HTML.
I'm in no way expecting anyone to fix this for me but a second pair of eyes and maybe a clue in which direction I should go would be helpful. Thank you so much!
Since I'm unfamiliar with your class website, some ideas for progressing:
You're not checking the status code from your login call. You may be getting a non-200 status code.
After you've confirmed the status code, check the cookie jar to ensure that a cookie has been saved. This is probably on the unlikelier side, but it's worth checking.
Lastly, attempt the same sequence with curl with -v (if you haven't already). That will give you more insight as to what is happening with your call plan.
I ended up ignoring some cookies accidentally while testing. Whoops...

How can I serve a CSS file and have dynamic routing?

I'm trying to set up an HTTP server in Go using only the standard library. The server should be able to accept requests of the form /:uuid and should be able to serve an index.html file as well as a css file imported in it. This is what my code looked like:
func indexHandler(w http.ResponseWriter, r *http.Request) {
// serve index.html
if r.URL.Path == "/" {
http.ServeFile(w, r, "./web/index.html")
} else {
// if the path is /randomwords, redirect to mapped URL
randomwords := r.URL.Path[1:]
url := getMappedURL(randomwords)
http.Redirect(w, r, url, http.StatusFound)
}
}
func main() {
http.HandleFunc("/", indexHandler)
log.Println("listening on port 5000")
http.ListenAndServe(":5000", nil)
}
This serves the html file and is able to accept requests like /:something but the problem is that it doesn't include the CSS file. After some googling, I changed the main function to this:
func main() {
fs := http.FileServer(http.Dir("web"))
http.Handle("/", fs)
log.Println("listening on port 5000")
http.ListenAndServe(":5000", nil)
}
This serves both the HTML and the CSS files but it doesn't allow routes of the form :something. I can't figure out how to have both of these features.
Your original solution was nearly there, all you have to do is add a branch:
if r.URL.Path == "/" {
http.ServeFile(w, r, "./web/index.html")
} else if r.URL.Path == "/styles.css" {
http.ServeFile(w, r, "./web/styles.css")
} else {
// ...
Of course this can be tailored as needed - you could check for any file ending in ".css" using strings.HasSuffix, for example.

How to remove the index.html path from url in golang

How do I remove the index.html from my URL bar e.g. localhost:8000/index.html
package main
import (
"net/http"
"io/ioutil"
)
func main() {
http.Handle("/", new(MyHandler))
http.ListenAndServe(":8000", nil)
}
type MyHandler struct {
http.Handler
}
func (this *MyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := "public" + req.URL.Path
data, err := ioutil.ReadFile(string(path))
if err == nil {
w.Write(data)
} else {
w.WriteHeader(404)
w.Write([]byte("404 - " + http.StatusText(404)))
}
}
Add a condition to serve index.html if the URL path is empty:
path := "public"
if req.URL.Path == "/" {
path += "/index.html"
} else {
path += req.URL.Path
}
Also, it would be a good idea to use net/http.ServeFile over manually writing the data to the output stream (see net/http#ServeContent's documentation to learn why this is a good idea).
It's also worth noting that a built-in handler for serving files exists.

Asynchronous Testing With Stream Processing

I'm very new to Go, so I may be misunderstanding something foundational about Go's async/stream handling, but here goes...
I'm trying to write some tests using ginkgo on a function I wrote that processes streams.
The processing side reads in newline-delimited text from a File until it encounters a special delimiter line at which point it tries to parse the text as JSON. The code looks like this:
func ParseConfig(inStream *os.File) (Config, error){
var header string
var stdin = bufio.NewScanner(inStream)
for stdin.Scan() {
line := stdin.Text()
if line == "|||" {
break;
}
header += line
}
// parse JSON here and return
}
My test looks something like this
Describe("ParseConfig()", func() {
It("should pass for a valid header", func(){
_, err := io.WriteString(stream, "{\"Key\": \"key\", \"File\": \"file\"}\n|||\n")
Expect(err).NotTo(HaveOccurred())
conf, err := parser.ParseConfig(stream)
Expect(err).NotTo(HaveOccurred())
Expect(conf.Key).To(Equal("key"))
})
})
Unfortunately, this yields a JSON parsing error, as it's trying to parse an empty string. I'm assuming that my problem is that I'm sending the string on the stream before I've told the ParseConfig() function to listen on that string for data? But I'm not entirely clear how I could refactor this to use proper go routines to first listen for data then send it.
Some of the potential solutions I saw were around the use of "channels" (with which I'm unfamiliar) but I was worried that this one need might not be worth a major refactor to introduce a whole new paradigm of concurrency.
Thanks!
Not sure if I understood correctly, but your ParseConfig should probably take an io.Reader instead of a *os.File. That way you can test it directly without worrying about concurrency.
file t_test.go:
package main
import (
"strings"
"testing"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var _ = ginkgo.Describe("ParseConfig()", func() {
ginkgo.It("should pass for a valid header", func() {
// really don't know what you were doing with your 'stream' variable
// This is a test, you should forge a test scenario and pass it to your config function
stream := strings.NewReader(`{"Key": "key", "File": "file"}` + "\n|||\n")
conf, err := ParseConfig(stream)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(conf.Key).To(gomega.Equal("key"))
})
})
func TestParseConfig(t *testing.T) {
ginkgo.RunSpecs(t, "Parse Config")
}
file main.go
package main
import (
"bufio"
"encoding/json"
"io"
"log"
"os"
)
type Config struct {
Key string
File string
}
func ParseConfig(inStream io.Reader) (*Config, error) {
var header string
var stdin = bufio.NewScanner(inStream)
for stdin.Scan() {
line := stdin.Text()
if line == "|||" {
break
}
header += line
}
c := &Config{}
// parse JSON here and return
if err := json.Unmarshal([]byte(header), c); err != nil {
return nil, err
}
return c, nil
}
func main() {
f, err := os.Open("config.json")
if err != nil {
log.Fatal(err)
}
ParseConfig(f)
}

Using FileServer to serve my single html page

I'm trying to build a sample web application demonstrating rest techniques using go at the back-end, serving json based requests and javascript, jquery in the front-end (I'm not using html/template package).
FileServer "returns a handler that serves HTTP requests with the contents of the file system rooted at root."
supose that I'm publishing my static folder that contains index.html and scripts folder holding some javascript files.
How can I prevent the client from viewing my js files (publishing just the index.html at /) ?
You can easily restrict the FileServer, which is a HttpHandler by wrapping another HttpHandler around that. For example, take this wrapper which ONLY allows *.js files to be served:
func GlobFilterHandler(h http.Handler, pattern string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
fileName := filepath.Base(path)
if ok, err := filepath.Match(pattern, fileName); !ok || err != nil {
if err != nil {
log.Println("Error in pattern match:", err)
}
http.NotFound(w, r)
return
}
h.ServeHTTP(w, r)
})
}
func main() {
fileHandler := http.FileServer(http.Dir("/tmp/dtest"))
wrappedHandler := GlobFilterHandler(fileHandler, "*.js")
}
You can find a blog post here which describes the basic idea pretty good.
Another option you have is to extend on http.Dir and make your own http.FileSystem implementation which does exactly what you want:
type GlobDir struct {
Dir http.Dir
Pattern string
}
func (d GlobDir) Open(name string) (http.File, error) {
baseName := filepath.Base(name)
if ok, err := filepath.Match(d.Pattern, baseName); !ok || err != nil {
if err != nil {
return nil, err
}
return nil, fmt.Errorf("%s not match GlobDir pattern.", baseName)
}
return d.Dir.Open(name)
}
func main() {
fileHandler := http.FileServer(GlobDir{
Dir: http.Dir("/tmp/dtest"),
Pattern: "*.js",
})
http.ListenAndServe(":8080", fileHandler)
}
The second solution implements the http.FileSystem interface which is accepted by http.FileServer.
It checks whether the input file name matches the supplied pattern and then hands control down to the original http.Dir. This is probably the way you want to go here.

Resources