How to stop the repetition of code when parsing files - html

I am repeating the same code of html files in every function. I just changed the name of one file in every function and that is ui/html/home.html in indexHandler() and ui/html/signup.html in signup()
Other file names are the same and the code of ParseFiles() is also the same in each function. How to prevent the code from repeating every time.
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
files := []string{"ui/html/home.html", "ui/html/footer.html", "ui/html/base.html",}
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func signup(w http.ResponseWriter, r *http.Request) {
files := []string{"ui/html/signup.html", "ui/html/footer.html", "ui/html/base.html",}
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/signup", signup)
log.Println("Starting the server at :4000")
http.ListenAndServe(":4000", nil)
}

You can make it a package variable.
https://play.golang.org/p/haDmat_kzkg
package main
import (
"fmt"
)
var (
files = []string{"ui/html/home.html", "ui/html/footer.html", "ui/html/base.html"}
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func signup(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/signup", signup)
log.Println("Starting the server at :4000")
http.ListenAndServe(":4000", nil)
}
If you need to add a template for a specific handler, create a new slice out of the global variable and append to it.
tmpl, err := template.ParseFiles(append(files,[]string{"..."})...)
I think you have to read a bit about effective go in general, https://golang.org/doc/effective_go#initialization
or the go tour https://tour.golang.org/welcome/1
A tip to apply is to compile the template during initialization, rather during the request processing. This will speed up your program.
package main
//...
var (
indexTpml = template.Must(template.ParseFiles(files...))
)
https://play.golang.org/p/TgjZYCul9M6
Using some helper functions, one can write,
package main
import (
"html/template"
"log"
"net/http"
)
func makeTpl(base string) *template.Template {
files := []string{base, "ui/html/footer.html", "ui/html/base.html"}
return template.Must(template.ParseFiles(files...))
}
var (
indexTpml = makeTpl("ui/html/home.html")
signupTpml = makeTpl("ui/html/signup.html")
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
indexTpml.Execute(w, nil)
}
func signup(w http.ResponseWriter, r *http.Request) {
signupTpml.Execute(w, nil)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/signup", signup)
log.Println("Starting the server at :4000")
http.ListenAndServe(":4000", nil)
}

Related

json: cannot unmarshal object into Go value of type []*main.Config

I'm new to golang and json, we are using gorilla mux library and I'd like to do a post request in postman. In config struct entries needs to be a map like that and in post server I need to have an array of *Config in postServer struct. I have 3 go files.
Service.go file is this:
package main
import (
"errors"
"github.com/gorilla/mux"
"mime"
"net/http"
)
type Config struct {
Id string `json:"id"`
entries map[string]string `json:"entries"`
}
type postServer struct {
data map[string][]*Config
}
func (ts *postServer) createPostHandler(w http.ResponseWriter, req *http.Request) {
contentType := req.Header.Get("Content-Type")
mediatype, _, err := mime.ParseMediaType(contentType)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if mediatype != "application/json" {
err := errors.New("Expect application/json Content-Type")
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
return
}
rt, err := decodeBody(req.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
id := createId()
ts.data[id] = rt
renderJSON(w, rt)
}
func (ts *postServer) getAllHandler(w http.ResponseWriter, req *http.Request) {
allTasks := []*Config{}
for _, v := range ts.data {
allTasks = append(allTasks, v...)
}
renderJSON(w, allTasks)
}
func (ts *postServer) getPostHandler(w http.ResponseWriter, req *http.Request) {
id := mux.Vars(req)["id"]
task, ok := ts.data[id]
if !ok {
err := errors.New("key not found")
http.Error(w, err.Error(), http.StatusNotFound)
return
}
renderJSON(w, task)
}
func (ts *postServer) delPostHandler(w http.ResponseWriter, req *http.Request) {
id := mux.Vars(req)["id"]
if v, ok := ts.data[id]; ok {
delete(ts.data, id)
renderJSON(w, v)
} else {
err := errors.New("key not found")
http.Error(w, err.Error(), http.StatusNotFound)
}
}
I wanted to test createPostHandler.
Then I have helper.go file where I decoded json into go and rendered into json:
package main
import (
"encoding/json"
"github.com/google/uuid"
"io"
"net/http"
)
func decodeBody(r io.Reader) ([]*Config, error) {
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
var rt []*Config
if err := dec.Decode(&rt); err != nil {
return nil, err
}
return rt, nil
}
func renderJSON(w http.ResponseWriter, v interface{}) {
js, err := json.Marshal(v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(js)
}
func createId() string {
return uuid.New().String()
}
and the last one go file is main.go where I have this:
package main
import (
"context"
"github.com/gorilla/mux"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
router := mux.NewRouter()
router.StrictSlash(true)
server := postServer{
data: map[string][]*Config{},
}
router.HandleFunc("/config/", server.createPostHandler).Methods("POST")
router.HandleFunc("/configs/", server.getAllHandler).Methods("GET")
router.HandleFunc("/config/{id}/", server.getPostHandler).Methods("GET")
router.HandleFunc("/config/{id}/", server.delPostHandler).Methods("DELETE")
// start server
srv := &http.Server{Addr: "0.0.0.0:8000", Handler: router}
go func() {
log.Println("server starting")
if err := srv.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
log.Fatal(err)
}
}
}()
<-quit
log.Println("service shutting down ...")
// gracefully stop server
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal(err)
}
log.Println("server stopped")
}
And JSON whad I did send is this:
{
"entries":["hello", "world"]
}
And error what I'm getting in postman is this:
json: cannot unmarshal object into Go value of type []*main.Config
I don't know what is a problem, maybe I'm sending wrong json or I just did something wrong in decodeBody, I needed to add [] in decodeBody in var rt []*Config because it wouldn't work otherwise.
Can someone help me to fix this please?
This is an example of how you can define a struct Config that you can parse your sample JSON into.
EDIT: field entries changed to map.
You can play with it on Playground.
package main
import (
"encoding/json"
"fmt"
)
type Config struct {
Id string `json:"id"`
Entries map[string]string `json:"entries"`
}
func main() {
str := `[{"id":"42", "entries":{"hello": "world"}}]`
var tmp []Config
err := json.Unmarshal([]byte(str), &tmp)
if err != nil {
fmt.Printf("error: %v", err)
}
var rt []*Config
for _, c := range tmp {
rt = append(rt, &c)
}
for _, c := range rt {
for k, v := range c.Entries {
fmt.Printf("id=%s key=%s value=%s\n", c.Id, k, v)
}
}
}

How to set up web server to perform POST Request in Go?

I want to set up a web server to perform a POST request. How does the post request get executed with the code below since only HandleFunc and ListenAndServe are defined in main function?
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
func post(w http.ResponseWriter, r *http.Request) {
const myurl string = "http://localhost:8000/"
request := strings.NewReader(`
{
"Name":"Tom",
"Age":"20"
}
`)
response, err := http.Post(myurl, "application/json", request)
content, err := ioutil.ReadAll(response.Body)
if err != nil {
panic(err)
}
fmt.Println(string(content))
defer response.Body.Close()
}
func main() {
http.HandleFunc("/", post)
log.Fatal(http.ListenAndServe(":8000", nil))
}
Here is a basic example of how you could go about it. I am using the same program to run both, the server and the client. This is just for demonstration purposes. You can of course make them separate programs.
// use struct to represent the data
// to recieve and send
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
// run the example
func main() {
// start the server in a goroutine
go server()
// wait 1 second to give the server time to start
time.Sleep(time.Second)
// make a post request
if err := client(); err != nil {
fmt.Println(err)
}
}
// basic web server to receive a request and
// decode the body into a user struct
func server() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
user := &Person{}
err := json.NewDecoder(r.Body).Decode(user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Println("got user:", user)
w.WriteHeader(http.StatusCreated)
})
if err := http.ListenAndServe(":8080", nil); err != http.ErrServerClosed {
panic(err)
}
}
// a simple client that posts a user to the server
func client() error {
user := &Person{
Name: "John",
Age: 30,
}
b := new(bytes.Buffer)
err := json.NewEncoder(b).Encode(user)
if err != nil {
return err
}
resp, err := http.Post("http://localhost:8080/", "application/json", b)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println(resp.Status)
return nil
}
Here is the working example: https://go.dev/play/p/34GT04jy_uA

How can I reduce redundant code of duplicate function in Golang?

I have a Rest API application to list all json data to browser. as long as I have more modules my code is more redundant. and complex.
func UserList(w http.ResponseWriter, r *http.Request) {
list := []models.User{}
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
func ProductList(w http.ResponseWriter, r *http.Request) {
list := []models.Product{}
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
func OrderList(w http.ResponseWriter, r *http.Request) {
list := []models.Order{}
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
Is there any better solution to make this code into just one function
Example
func List(w http.ResponseWriter, r *http.Request) {
list := ??? List of struct here ???
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
you can do something like this:
func List(list interface{}, w http.ResponseWriter, r *http.Request,) {
db.Find(list)
json.NewEncoder(w).Encode(list)
}
If you pass the model type as a request param, this should do it (including error handling):
func List(w http.ResponseWriter, r *http.Request) {
var list interface{}
switch r.FormValue("model") {
case "user":
list = new([]models.User)
case "product":
list = new([]models.Product)
case "order":
list = new([]models.Order)
default:
http.Error(w, "invalid type", http.StatusBadRequest)
return
}
if err := db.Find(list); err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(list); err != nil {
log.Printf("json encoding error: %v", err)
}
}
Another option is to build a registry of types, and even the slice creation can be factored out, using the help of reflect:
var reg = map[string]reflect.Type{
"user": reflect.TypeOf((*models.User)(nil)).Elem(),
"product": reflect.TypeOf((*models.Product)(nil)).Elem(),
"order": reflect.TypeOf((*models.Order)(nil)).Elem(),
}
func List(w http.ResponseWriter, r *http.Request) {
etype := reg[r.FormValue("model")]
if etype == nil {
http.Error(w, "invalid type", http.StatusBadRequest)
return
}
list := reflect.New(reflect.SliceOf(etype)).Interface()
if err := db.Find(list); err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(list); err != nil {
log.Printf("json encoding error: %v", err)
}
}
Given that you are calling db.Find(&list) I assume that they share a common interface. In which case you could wrap your handler calls like so;
func ListHandler(list <YOUR_INTERFACE>) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
}
In your call;
http.HandleFunc("/user/list", ListHandler([]models.User{}))
http.HandleFunc("/product/list", ListHandler([]models.Product{}))
http.HandleFunc("/order/list", ListHandler([]models.Order{}))

Golang saving to json file

I am wondering why saving to json file does'nt work as I expected.
-If I input values in the fields and click submit button
-The form will submit and the process function executes
-The process.html renders the input values.
-The input values not saving to the json file.
import (
"net/http"
"html/template"
"os"
"encoding/json"
)
var tpl *template.Template
type Data struct {
First string `json:"First"`
Last string `json:"Last"`
}
func init() {
tpl = template.Must(template.ParseGlob("templates/*.gohtml"))
}
func main() {
http.HandleFunc("/", index);
http.HandleFunc("/process", process);
http.ListenAndServe(":80", nil);
}
func index(w http.ResponseWriter, r *http.Request) {
tpl.ExecuteTemplate(w, "index.gohtml", nil)
}
func process(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
f, err := os.Open("name.json");
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer f.Close();
data := new(Data)
data.First = r.FormValue("first");
data.Last = r.FormValue("last");
b, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
f.Write(b)
f.Close()
tpl.ExecuteTemplate(w, "process.gohtml", data)
}
I believe that os.Open defaults to read-only. I think you want something like os.OpenFile.

Use sql connection in another func

How init mysql connection in main function and place it another function? Or init connection in another place and use it anywhere?
Something like this below(this example wrong!)
package main
import (
"fmt"
"net/http"
_ "github.com/go-sql-driver/mysql"
"database/sql"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
res, err := stmt.Exec("test", "test", "test")
}
func main() {
db, err := sql.Open("mysql", "connection")
if err != nil {
panic(err)
}
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":3000", nil)
}
Thanks anyway!
Make it global:
var db *sql.DB
func indexHandler(w http.ResponseWriter, r *http.Request) {
stmt, err := db.Prepare(...
...
}
func main() {
var err error
db, err = sql.Open("mysql", "connection")
...
or create handler as closure:
func createIndexHandler(db *sql.DB) (func (http.ResponseWriter, *http.Request)) {
return func(w http.ResponseWriter, r *http.Request) {
stmt, err := db.Prepare(...
...
}
}
func main() {
db, err := sql.Open("mysql", "connection")
...
http.HandleFunc("/", createIndexHandler(db))