gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
78.71k stars 8.01k forks source link

Json not work #149

Closed louishust closed 9 years ago

louishust commented 9 years ago

Hi all, I tried JSON with gin, but it does not work! The html like below:

<form class="form-signin">
              <input id="username" type="text" class="form-control" placeholder="username" required autofocus>
              <input id="password" type="password" class="form-control" placeholder="password" required>
              <button id="btnSubmit" class="btn btn-lg btn-primary btn-block" type="submit">
                Sign in</button>
              <label class="checkbox pull-left">
                <input type="checkbox" value="remember-me">

The js like below:

$(document).ready(function () {
  //event handler for submit button
  $("#btnSubmit").click(function () {
    //collect userName and password entered by users
    var username = $("#username").val();
    var password = $("#password").val();

    alert(username);
    alert(password);
    //call the authenticate function
    authenticate(username, password);
  });
});

//authenticate function to make ajax call
function authenticate(userName, password) {
  $.ajax
  ({
    type: "POST",
    //the url where you want to sent the userName and password to
    url: "/login",
    dataType: 'json',
    async: false,
    //json object to sent to the authentication url
    data: '{"username": "' + userName + '", "password" : "' + password + '"}',
    success: function () {
      //do any process for successful authentication here
    }
  })
}

and the gin router function like below:

type LoginJSON struct {
    username string `json:"username" binding:"required"`
    password string `json:"password" binding:"required"`
}

func API_Login_Check(c *gin.Context) {
    data, err := Asset("static/login.html")

    var json LoginJSON
    ret := c.Bind(&json)

    fmt.Println(ret)
    fmt.Println(json.username)
    fmt.Println(json.password)
    ...
}

When run it, get the following error:

2014/11/15 22:09:51 PANIC: reflect.Value.Interface: cannot return value obtained from unexported field or method
/usr/local/go/src/pkg/reflect/value.go:1081 (0x8a7bc)
    valueInterface: panic("reflect.Value.Interface: cannot return value obtained from unexported field or method")
/usr/local/go/src/pkg/reflect/value.go:1070 (0x8a68c)
    Value.Interface: return valueInterface(v, true)
/Users/loushuai/workspace/go/src/github.com/gin-gonic/gin/binding/binding.go:177 (0xf4eef)
    Validate: fieldValue := val.Field(i).Interface()
/Users/loushuai/workspace/go/src/github.com/gin-gonic/gin/binding/binding.go:63 (0xf3d6c)
    formBinding.Bind: return Validate(obj)
<autogenerated>:6 (0xf577a)
/Users/loushuai/workspace/go/src/github.com/gin-gonic/gin/context.go:216 (0x59dff)
    (*Context).BindWith: if err := b.Bind(c.Request, obj); err != nil {
/Users/loushuai/workspace/go/src/github.com/gin-gonic/gin/context.go:212 (0x59b0f)
    (*Context).Bind: return c.BindWith(obj, b)
/Users/loushuai/workspace/go/src/pgweb/api.go:46 (0x22b8)
    API_Login_Check: ret := c.Bind(&json)

I'm new to gin and web develop, any idea will be appreciated.

javierprovecho commented 9 years ago

Easy, your error is located where you are declaring the struct:

type LoginJSON struct {
    username string `json:"username" binding:"required"`
    password string `json:"password" binding:"required"`
}

As part of GoLang, if the var's name start uncapitalized, it will be private, that's why neither Go or Gin don't let you access it content.

You just need to capitalize first letter of each variable you want to make public. Here is a full working example:

package main

import (
    "github.com/gin-gonic/gin"
    "fmt"
)

func main() {
    g := gin.New()

    // Logging middleware
    g.Use(gin.Logger())

    // Recovery middleware
    g.Use(gin.Recovery())

    g.POST("/", API_Login_Check)

    // Serve
    g.Run(":3000")
}

type LoginJSON struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

func API_Login_Check(c *gin.Context) {
    var json LoginJSON
    c.Bind(&json)
    c.JSON(200, json)
    // Print response
    fmt.Println(json.Username, json.Password)
}

After starting the server you can try quickly with curl CLI, like this:

curl -H "Content-Type: application/json" -d '{"username":"xyz","password":"xyz"}' http://localhost:3000/ 
louishust commented 9 years ago

Many thanks to @javierprovecho !

I tried your example works, but when I use ajax to pass the json data, it seems that the data is not passed.

The ajax function like below:

function authenticate(username, password) {
  $.ajax
  ({
    type: "POST",
    //the url where you want to sent the userName and password to
    url: "/login",
    dataType: 'json',
    async: false,
    //json object to sent to the authentication url
    data: '{"username": "' + username + '", "password" : "' + password + '"}',
    success: function () {
      //do any process for successful authentication here
    }
  });
}

And got the following error:

[GIN] 2014/11/16 - 10:07:14 | 200 |    212.866us | 127.0.0.1:57189 POST /login
Error #01: Required Username
     Meta: Operation aborted

The POST is passed to the API_LoginCheck, But the data is missed!! When I use curl it works well, can you help me for this? I know that maybe I use ajax the wrong way,but i can not find the point. `##`

montanaflynn commented 9 years ago

First of all you might be getting CORS issues which would result in that server response and also an error client-side. See the CORS middleware in the Go code below if you are indeed running into this issue.

Secondly, dataType in the jQuery request object is the wrong option as it sets the Accept header. You want to use contentType which sets the content-type header.

Here it is all together and working:

authenticate("user", "pass");

function authenticate(username, password) {
  $.ajax({
    type: "POST",
    url: "http://localhost:3000/login",
    contentType: "application/json",
    async: false,
    data: '{"username": "' + username + '", "password" : "' + password + '"}',
    success: function (res) {
      console.log(res);
    }
  });
}
package main

import (
    "github.com/gin-gonic/gin"
    "fmt"
)

func main() {
    g := gin.New()

    // Logging middleware
    g.Use(gin.Logger())

    // Recovery middleware
    g.Use(gin.Recovery())

    // CORS middleware
    g.Use(CORSMiddleware())

    g.POST("/login", LoginCheck)

    // Serve
    g.Run(":3000")
}

type LoginJSON struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

func LoginCheck(c *gin.Context) {
    var json LoginJSON
    c.Bind(&json)
    c.JSON(200, json)
    // Print response
    fmt.Println(json.Username, json.Password)
}

func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
        if c.Request.Method == "OPTIONS" {
            c.Abort(200)
            return
        }
        c.Next()
    }
}
louishust commented 9 years ago

Lots thanks to @javierprovecho , I change the "data-type" to "content-type", and it works, thank you very much for help!!

montanaflynn commented 9 years ago

Glad it worked out for you!