req.FormValue not mapping req.body values - rest

I'm trying to pick out a value in the request.body but I keep getting an empty string.
The Form map is also coming up as empty.
What am I doing wrong?
package user
import (
"fmt"
"net/http"
"../../types"
)
func PostTest(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
x := r.FormValue("name")
fmt.Println(x)
}
The body of the post request:
{
"name":"Tom",
"age":25
}

The reason is because the request body is not valid form data, but a blob of JSON data. You will need to parse it before being able to extract the name, e.g.:
type data struct {
Name string
Age int
}
func PostTest(w http.ResponseWriter, r *http.Request) {
var d data
json.NewDecoder(r.Body).Decode(&d) // Error handling omitted.
fmt.Println(d.Name)
}
Here's a Playground demonstrating this. I have omitted error handling for brevity.

Related

Golang serialize form-urlencoded to object

I've made a post request where I'm sending data as JSON and this code creates a new row in the DB.
json.NewDecoder(r.Body).Decode(&user)
DB.Create(&user)
json.NewEncoder(w).Encode(user)
But parsing the form data shows this error
I figured this is how I would read every individual value
for key, value := range r.PostForm {
fmt.Printf("Key:%s, Value:%s\n", key, value)
}
My model looks like this
type User struct {
gorm.Model
FirstName string `json:"firstname"`
LastName string `json:"lastname"`
Email string `json:"email"`
}
How would I convert this to user and insert to DB?
Another way to do it (a bit overengineered, better to use r.FormValue), without having to type the fields again, such as r.FormValue("firstname"), etc if you already know the data, types etc.
func handleUser(w http.ResponseWriter, r *http.Request) {
user := User{}
userType := reflect.TypeOf(user)
for i := 0; i < userType.NumField(); i++ {
// Get the JSON tag of the field, use it for the r.FormValue
f := userType.Field(i).Tag.Get("json")
if f != "" {
reflect.ValueOf(&user).Elem().FieldByName(
userType.Field(i).Name).SetString(r.FormValue(f))
}
}
// DB.Create(&user) need a reference to DB here.
json.NewEncoder(w).Encode(user)
}
Note: This is just an example that will work for string fields, may panic otherwise because it's calling SetString. Additionally, a field may not exist and could panic when trying to access it in the FieldByName(..., but I think your question was geared to this direction of not having to type each field, so leaving it as an example.
The right way to do it, as Cerise Limón pointed out, would be
func handleUser(w http.ResponseWriter, r *http.Request) {
user := User{}
user.FirstName = r.FormValue("firstname")
user.LastName = r.FormValue("lastname")
user.Email = r.FormValue("email")
// DB.Create(&user) need a reference to DB here.
json.NewEncoder(w).Encode(user)
}
Note that you can also add a method for populating the user with the form if you do this in several places, for example
func (u *User) setFormData(r *http.Request) {
u.FirstName = r.FormValue("firstname")
u.LastName = r.FormValue("lastname")
u.Email = r.FormValue("email")
}
And then use it like this
func handleUser(w http.ResponseWriter, r *http.Request) {
user := User{}
user.setFormData(r)
// DB.Create(&user)
json.NewEncoder(w).Encode(user)
}

go lang net/http request pattern

I'm writing a simple rest api server and I cannot get route dynamic url using net/http
http://localhost:8080/book/name
where name can be any string.
this was my attempt:
func viewIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, 'html')
}
http.HandleFunc("/book/{name}", view)
this does not work, there is some cryptic note in the documentation of HandleFunc:
The documentation for ServeMux explains how patterns are matched.
This is a working solution.
The only interesting part it that patterns ending with / will be treated as prefix matcher for all urls.
So pattern /book/ will match:
/book/
/book/a
/book/bb
/book/a/b
sample simplified code:
package main
import (
"fmt"
"strings"
"net/http"
)
func book(name string) {
fmt.Printf("requesting book '%s'\n",name);
}
func view(w http.ResponseWriter, r *http.Request) {
url := r.URL.String()
if strings.HasPrefix(url, "/book/"){
name := url[6:]
book(name)
fmt.Fprint(w, name)
}
}
func main() {
http.HandleFunc("/book/", view)
http.ListenAndServe("localhost:8080", nil)
}

How to insert multiple values to database

In my Go application I have such route:
router.HandleFunc("/api/users_groups_relationship/{user_id:[0-9]+}", controllers.CreateUsersGroupsRelationship).Methods("POST")
I make POST request. In body of that request I send such JSON which looks like this:
{
"groups": [1, 2, 3]
}
As you can see groups key has array of ids as value. User can be in several groups. I am trying to insert multiple values to PostgreSQL database.
How to get the value of a specific key in request body?
Is there any other best way to insert multiple values in database by Go?
My code:
var CreateUsersGroupsRelationship = func(responseWriter http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
userID := vars["user_id"]
fmt.Println(userID)
var groups []int
groups = request.Body("groups") // ???
for i := 1; i < len(groups); i++ {
fmt.Println(groups[i])
_, _ := database.DBSQL.Exec("INSERT INTO users_groups (user_id, group_id) VALUES ($1, $2);", userID, groups[i])
}
utils.ResponseWithSuccess(responseWriter, http.StatusOK, "All new records successfully created.")
}
You can define a struct for Request object and then unmarshal JSON into it.
package main
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
"encoding/json"
)
//Request is our request body.
type Request struct {
Groups []int `json:"groups"`
}
//JsonTest1 is the http handler.
func JsonTest1(w http.ResponseWriter, r *http.Request) {
req := new(Request)
//decode request to struct.
if err := json.NewDecoder(r.Body).Decode(&req); err != nil{
w.WriteHeader(400) //bad request
}
w.WriteHeader(200)
b, _ := json.Marshal(req)
w.Write(b)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
}
func main(){
fmt.Printf("starting backend server\n")
root := mux.NewRouter()
root.HandleFunc("/foo", JsonTest1)
webServer := &http.Server{Addr: ":4000", Handler: root}
webServer.ListenAndServe()
}
In case your body is quite generic, you can also unmarshal to map[string]interface{}.
Try with
curl -XPOST -d '{"groups": [1]}' http://localhost:4000/foo

How do I place the AVG of a row into a new column?

Hey everyone I am using golang to build a super simple API. I have this json data being passed from a POST request and being stored in my DB. I would like to take the tts data which is an integer array and average that array and place it in the ttc column and return that number on the json response. I am having a hard time doing that any help would be greatly appreciated. My source code is below as well as my DB Model. I know I would have to use the AVG() function somehow in postgres but I am brand new to postgres so I am super confused.
main.go
package main
import (
"encoding/json"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/lib/pq"
"github.com/rs/cors"
"log"
"net/http"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
type Resource struct {
gorm.Model
Name string
TTS pq.Int64Array `gorm:"type:integer[]"`
TTC int
}
var db *gorm.DB
var err error
func main() {
router := mux.NewRouter()
db, err = gorm.Open(
"postgres",
"host=localhost"+" user=postgres"+
" dbname=Shoes"+" sslmode=disable password=root")
if err != nil {
panic("failed to connect database")
}
defer db.Close()
db.AutoMigrate(&Resource{})
router.HandleFunc("/resources", GetResources).Methods("GET")
router.HandleFunc("/resources/{id}", GetResource).Methods("GET")
router.HandleFunc("/resources", CreateResource).Methods("POST")
router.HandleFunc("/resources/{id}", DeleteResource).Methods("DELETE")
handler := cors.Default().Handler(router)
log.Fatal(http.ListenAndServe(":8080", handler))
}
func GetResources(w http.ResponseWriter, r *http.Request) {
var resources []Resource
db.Find(&resources)
json.NewEncoder(w).Encode(&resources)
}
func GetResource(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var resource Resource
db.First(&resource, params["id"])
json.NewEncoder(w).Encode(&resource)
}
func CreateResource(w http.ResponseWriter, r *http.Request) {
var resource Resource
json.NewDecoder(r.Body).Decode(&resource)
db.Create(&resource)
json.NewEncoder(w).Encode(&resource)
}
func DeleteResource(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var resource Resource
db.First(&resource, params["id"])
db.Delete(&resource)
var resources []Resource
db.Find(&resources)
json.NewEncoder(w).Encode(&resources)
}
I am thinking I could do something like
db.Select("AVG(tts)")
I am just not sure how to put that result in the column ttc
Since the json of the post request already contains the tts_data, you can get the average before setting it in the database
sum := 0
for _, i := range tts_data {
sum += i
}
avg := sum / len(tts_data)
// save the data in your db
rs := Ressource{Name: "name", TTS: tts_data, ttc: avg}
b := db.Create(&rs)
if b {
// send all the resource
json.NewEncoder(w).Encode(&rs)
// or send only the avg
json.NewEncoder(w).Encode(struct{avg: int}{avg: avg})
} else {
// handle error
}

Go Gorilla Mux not routing as expected

I am having issues getting the Gorilla Mux library for Go to work. From the documentation I have read and all the debugging I've done, I cannot seem to figure out what the problem is. Here's what I've got for routing:
Folder structure:
project_root
|-- main.go
|-- routes
|-- routes.go
|-- user.go
main.go:
package main
import (
"fmt"
"net/http"
"./routes"
)
func main() {
r := routes.CreateRoutes(http.Dir("./content"))
http.Handle("/", r)
err := http.ListenAndServe(fmt.Sprintf("%s:%d", "localhost", 8000), nil)
if err != nil {
fmt.Println("Error: ", err)
}
}
routes/routes.go
package routes
import (
"net/http"
"github.com/gorilla/mux"
)
func CreateRoutes(staticDir http.FileSystem) *mux.Router {
r := mux.NewRouter()
// Serve static pages (i.e. web app)
r.PathPrefix("/").Handler(http.FileServer(staticDir))
// Serve User Pages
createUserRoutes(r)
return r
}
routes/user.go
package routes
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func createUserRoutes(r *mux.Router) {
user := r.PathPrefix("/user/").Subrouter()
// Create a new user
user.Path("/new").Methods("PUT").HandlerFunc(newUserHandler)
// Remove a user
user.Path("/remove/{username:[a-z][a-z0-9]+}").Methods("DELETE").HandlerFunc(removeUserHandler)
// Update a user
user.Path("update/{username:[a-z][a-z0-9]+").Methods("POST").HandlerFunc(updateUserHandler)
// Get a user (Get user information)
user.Path("/{username:[a-z][a-z0-9]+").Methods("GET").HandlerFunc(getUserHandler)
}
func newUserHandler(resp http.ResponseWriter, req *http.Request) {
// Do something that might cause an error
if err != nil {
fmt.Println(err)
resp.WriteHeader(409)
resp.Write([]byte(err.Error()))
} else {
fmt.Println("Created new user")
resp.WriteHeader(201)
resp.Write([]byte("Created new user"))
}
}
func removeUserHandler(resp http.ResponseWriter, req *http.Request) {
}
func updateUserHandler(resp http.ResponseWriter, req *http.Request) {
}
func getUserHandler(resp http.ResponseWriter, req *http.Request) {
}
Whenever I make a request to root path of the server (i.e. the path that serves the static content), the server responds as intended, with the main page. However, any other calls result in a 404 response (I test requests using cURL). For example, a malformed request to http://localhost:8000/user/new should return a 409, but instead returns a 404. Same if I expect a 201 response.
Everything looks right and I've triple checked it, but I cannot figure out what the issue here is.
Turns out the solution was simple (like it usually is). This line in routes.go
r.PathPrefix("/").Handler(http.FileServer(staticDir))
was causing the unintended routing. When PathPrefix is used, it seems to route all URLs to the first matching prefix (in this case this prefix). This explains why static files were being served, but nothing else works.
The fix is to use the Path function instead. There's a subtle difference as explained in the docs; PathPrefix "matches if the given template is a prefix of the full URL path", whereas Path does not. Hence the line above now looks like this to solve the issue I was having:
r.Path("/").Handler(http.FileServer(staticDir))