I'm new to Go. But am playing with a REST Api. I cant get the same behavior out of json.Marshal as json.Encoder in two functions i have
I wanted to use this function to gzip my responses:
func gzipFast(a *[]byte) []byte {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
defer gz.Close()
if _, err := gz.Write(*a); err != nil {
panic(err)
}
return b.Bytes()
}
But this function returns this:
curl http://localhost:8081/compressedget --compressed --verbose
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /compressedget HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.47.0
> Accept: */*
> Accept-Encoding: deflate, gzip
>
< HTTP/1.1 200 OK
< Content-Encoding: gzip
< Content-Type: application/json
< Date: Wed, 24 Aug 2016 00:59:38 GMT
< Content-Length: 30
<
* Connection #0 to host localhost left intact
Here is the go function:
func CompressedGet(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
box := Box{Width: 10, Height: 20, Color: "gree", Open: false}
box.ars = make([]int, 100)
for i := range box.ars {
box.ars[i] = i
}
//fmt.Println(r.Header.Get("Content-Encoding"))
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Encoding", "gzip")
b, _ := json.Marshal(box)
//fmt.Println(len(b))
//fmt.Println(len(gzipFast(&b)))
fmt.Fprint(w, gzipFast(&b))
//fmt.Println(len(gzipSlow(b)))
//gz := gzip.NewWriter(w)
//defer gz.Close()
//json.NewEncoder(gz).Encode(box)
r.Body.Close()
}
But when i uncomment:
//gz := gzip.NewWriter(w)
//defer gz.Close()
//json.NewEncoder(gz).Encode(box)
it works fine.
I would avoid gzip-ing []byte manually. You can easily use already existing writers from standard library. Additionally, take a look on compress/flate which I think should be use instead of gzip.
package main
import (
"net/http"
"encoding/json"
"compress/gzip"
"log"
)
type Box struct {
Width int `json:"width"`
}
func writeJsonResponseCompressed(w http.ResponseWriter, r *http.Request) {
box := &Box{Width: 10}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Encoding", "gzip")
body, err := json.Marshal(box)
if err != nil {
// Your error handling
return
}
writer, err := gzip.NewWriterLevel(w, gzip.BestCompression)
if err != nil {
// Your error handling
return
}
defer writer.Close()
writer.Write(body)
}
func main() {
http.HandleFunc("/compressedget", writeJsonResponseCompressed)
log.Fatal(http.ListenAndServe(":8081", nil))
}
You need to flush or close your gzip writer before accessing the underlying bytes, e.g.
func gzipFast(a *[]byte) []byte {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write(*a); err != nil {
gz.Close()
panic(err)
}
gz.Close()
return b.Bytes()
}
Otherwise what's been buffer in the gzip writer, but not yet written out to the final stream isn't getting collected up.
I think the problem is the use of fmt.Fprint(w, gzipFast(&b)).
If you look to the definition of gzipFast it returns a []byte. You are putting this slice into the print function, which is "printing" everything into w.
If you look at the definition of the io.Writer:
type Writer interface {
Write(p []byte) (n int, err error) }
You see that the writer can handle []byte as input.
Instead of fmt.Fprint(w, gzipFast(&b)) you should use w.Write(gzipFast(&b)). Then you don't need to uncomment:
//gz := gzip.NewWriter(w)
//defer gz.Close()
//json.NewEncoder(gz).Encode(box)
Everything as a small example, which shows what is happening in your code:
https://play.golang.org/p/6rzqLWTGiI
Related
Go here using Chi renderer for a basic REST service. I have the following structs and functions:
type Order struct {
OrderId uuid.UUID `json:"orderId",gorm:"type:uuid;primary_key;not null;default gen_random_uuid()"`
Quantity int `json:"quantity",gorm:"not null"`
Status string `json:"status",gorm:"not null"`
}
func (o *Order) Bind(r *http.Request) error {
return nil
}
func (o *Order) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}
func NewOrdersList(orders []Order) []render.Renderer {
list := []render.Renderer{}
for _, order := range orders {
list = append(list, &order)
}
return list
}
func GetOrderByIdHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderId := chi.URLParam(r, "orderId")
order, err := fetchOrder(orderId)
if err != nil {
render.Render(w, r, NewInternalServerError(err))
return
}
log.Info("order status is " + order.Status)
render.Bind(r, &order)
return
}
}
When I run this and hit the endpoint that invokes the GetOrderByIdHandler() function, I get back a 200 OK/Success. However there is no JSON in the response body, whereas I would have expected a marshalled JSON payload representing an "order", such as:
{
"orderId": "12345",
"quantity": 1,
"status": "SENT"
}
However my curl shows nothing in the response body:
$ curl -i -H "Content-Type: application/json" -H "Accept: application/json" -X GET http://localhost:9400/myapp/v1/orders/12345
HTTP/1.1 200 OK
Vary: Origin
Date: Wed, 24 Jun 2020 07:09:30 GMT
Content-Length: 0
Any idea where I'm going awry? I do see the log statement print out the order status right before calling bind, so I know its not a null/empty order instance.
render.Bind is input-only, i.e. for decoding the request payload. Instead use render.JSON to send a json response.
func GetOrderByIdHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderId := chi.URLParam(r, "orderId")
order, err := fetchOrder(orderId)
if err != nil {
render.Render(w, r, NewInternalServerError(err))
return
}
log.Info("order status is " + order.Status)
render.JSON(w, r, order)
}
}
Or, alternatively, you could also use the standard approach: import the encoding/json package and then use it like so:
func GetOrderByIdHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderId := chi.URLParam(r, "orderId")
order, err := fetchOrder(orderId)
if err != nil {
render.Render(w, r, NewInternalServerError(err))
return
}
log.Info("order status is " + order.Status)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(order); err != nil {
render.Render(w, r, NewInternalServerError(err))
}
}
}
Also note that the proper format for multiple struct tags is "space delimited" not "comma separated". For example: json:"quantity" gorm:"not null" is correct, while json:"quantity",gorm:"not null" is not.
If I create a struct, how do I send it with a HTTP GET request to a web server endpoint?
package main
import (
"encoding/json"
"net/http"
)
type Payload struct {
Endpoint string `json:"endpoint"`
Data map[string]interface{} `json:"data"`
}
/*
eg.
{"endpoint":"some-service", "data": {"userID": "abc123"}}
*/
func main() {
http.HandleFunc("/service", func(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var p Payload
err := decoder.Decode(&p)
if err != nil {
panic(err)
}
// How to attach 'p' ?
resp, err := http.Get("www.example.com/" + p.Endpoint) // Add "data": p.Data
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// handle response here
})
http.ListenAndServe(":8080", nil)
}
The endpoint receiving this data would ideally interpret it as JSON.
HTTP GET requests do not allow a request body.
If you must do it with a GET, basically you have 2 options: add the data as a query parameter, or send it in an HTTP Header field.
Note that both the URL and header fields have length limits, so if you want to "attach" a long JSON text, it might fail. To send arbitrary data, you should use another method, e.g. POST.
Example adding it as a query param:
u, err := url.Parse("http://www.example.com")
if err != nil {
panic(err)
}
params := url.Values{}
params.Add("data", `{"a":1,"b":"c"}`)
u.RawQuery = params.Encode()
// use u.String() as the request URL
Example sending it in a Header field:
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("X-Data", `{"a":1,"b":"c"}`)
resp, err := client.Do(req)
I have a JSON object of type []byte that I created from a struct using json.Marshal. I want to GZip the JSON before posting it to my endpoint. The following does not work:
gz := gzip.NewWriter(myJSON)
because []byte does not implement io.Writer.
Is there some very simple way I can do this once I already have my JSON created?
Compress to a buffer and post that buffer.
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
gz.Write(myJSON)
gz.Close()
Because a *bytes.Buffer statisifies the io.Reader interface, you can use the buffer directly when creating the request.
req, err := http.NewRequest("POST", url, &buf)
if err != nil {
// handle error
}
req.Header.Set("Content-Encoding", "gzip")
req.Header.Set("Content-Type", "application/json; charset=utf-8")
The best option is to stream your JSON marshaling to your gzip writer:
func compressJSON(w io.Writer, i interface{}) error {
gz := gzip.NewWriter(w)
if err := json.NewEncoder(gz).Encode(i); err != nil {
return err
}
return gz.Close()
}
This has the advantage of not buffering your json in memory temporarily, so it will be faster and use less RAM.
If you need this as an io.Reader, such as for the body of an HTTP request, you can use a pipe to do the conversion:
r, w := io.Pipe()
go func() {
err := compressJSON(w, someObject)
w.CloseWithError(err)
}()
req, err := http.NewRequest("POST", "http://example.com/", r)
I'm new to Go and trying to write a simple web crawler. I'm using duck duck go's api and trying to display search results.
https://duckduckgo.com/api
This is my code -
package main
import (
"fmt"
"net/http"
)
func main() {
getDuckDuckGo("food")
}
func getDuckDuckGo(keyword string) <- chan string{
resp, _ := http.Get("http://api.duckduckgo.com/?q=" + keyword + "&format=json&pretty=1")
c := make(chan string)
fmt.Println(resp)
var respMap map[string]interface{}
fmt.Println(respMap)
fmt.Println(respMap)
return c
}
My resp println gives me this -
&{200 OK 200 HTTP/1.1 1 1 map[Connection:[keep-alive] Content-Type:[application/x-javascript] Date:[Sat, 20 Dec 2014 00:41:49 GMT] Cache-Control:[max-age=1] Expires:[Sat, 20 Dec 2014 00:41:50 GMT] Server:[nginx] X-Duckduckgo-Locale:[en_US]] 0xf840053c20 -1 [chunked] false map[] 0xf84007c000}
Rather than any json.
Am I doing the GET request correctly?
At least, you should do below things:
Check error of http.Get()
Get io.Reader by resp.Body for HTTP body data
Use json.Decoder to decode json
Your getDuckDuckGo() should be became like this:
func getDuckDuckGoImproved(k string) (map[string]interface{}, error) {
resp, err := http.Get("http://api.duckduckgo.com/?=" + k + "&format=json")
if err != nil {
return nil, err
}
defer resp.Body.Close()
r := make(map[string]interface{})
d := json.NewDecoder(resp.Body)
if err := d.Decode(&r); err != nil {
return nil, err
}
return r, nil
}
I tried working with Apiary and made a universal template to send JSON to mock server and have this code:
package main
import (
"encoding/json"
"fmt"
"github.com/jmcvetta/napping"
"log"
"net/http"
)
func main() {
url := "http://restapi3.apiary.io/notes"
fmt.Println("URL:>", url)
s := napping.Session{}
h := &http.Header{}
h.Set("X-Custom-Header", "myvalue")
s.Header = h
var jsonStr = []byte(`
{
"title": "Buy cheese and bread for breakfast."
}`)
var data map[string]json.RawMessage
err := json.Unmarshal(jsonStr, &data)
if err != nil {
fmt.Println(err)
}
resp, err := s.Post(url, &data, nil, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("response Status:", resp.Status())
fmt.Println("response Headers:", resp.HttpResponse().Header)
fmt.Println("response Body:", resp.RawText())
}
This code doesn't send JSON properly, but I don't know why. The JSON string can be different in every call. I can't use Struct for this.
I'm not familiar with napping, but using Golang's net/http package works fine (playground):
func main() {
url := "http://restapi3.apiary.io/notes"
fmt.Println("URL:>", url)
var jsonStr = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("response Body:", string(body))
}
you can just use post to post your json.
values := map[string]string{"username": username, "password": password}
jsonValue, _ := json.Marshal(values)
resp, err := http.Post(authAuthenticatorUrl, "application/json", bytes.NewBuffer(jsonValue))
If you already have a struct.
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
)
// .....
type Student struct {
Name string `json:"name"`
Address string `json:"address"`
}
// .....
body := &Student{
Name: "abc",
Address: "xyz",
}
payloadBuf := new(bytes.Buffer)
json.NewEncoder(payloadBuf).Encode(body)
req, _ := http.NewRequest("POST", url, payloadBuf)
client := &http.Client{}
res, e := client.Do(req)
if e != nil {
return e
}
defer res.Body.Close()
fmt.Println("response Status:", res.Status)
// Print the body to the stdout
io.Copy(os.Stdout, res.Body)
Full gist.
In addition to standard net/http package, you can consider using my GoRequest which wraps around net/http and make your life easier without thinking too much about json or struct. But you can also mix and match both of them in one request! (you can see more details about it in gorequest github page)
So, in the end your code will become like follow:
func main() {
url := "http://restapi3.apiary.io/notes"
fmt.Println("URL:>", url)
request := gorequest.New()
titleList := []string{"title1", "title2", "title3"}
for _, title := range titleList {
resp, body, errs := request.Post(url).
Set("X-Custom-Header", "myvalue").
Send(`{"title":"` + title + `"}`).
End()
if errs != nil {
fmt.Println(errs)
os.Exit(1)
}
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
fmt.Println("response Body:", body)
}
}
This depends on how you want to achieve. I made this library because I have the same problem with you and I want code that is shorter, easy to use with json, and more maintainable in my codebase and production system.
Example post request for http or https
//Encode the data
postBody, _ := json.Marshal(map[string]string{
"name": "Test",
"email": "Test#Test.com",
})
responseBody := bytes.NewBuffer(postBody)
//Leverage Go's HTTP Post function to make request
resp, err := http.Post("https://postman-echo.com/post", "application/json", responseBody)
//Handle Error
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
defer resp.Body.Close()
//Read the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
sb := string(body)
log.Printf(sb)
Use io.Pipe for large request bodies as mentioned in another answer. This approach avoids building the entire request body in memory by streaming the data from the JSON encoder to the network.
This answer builds on the other answer by showing how to handle errors. Always handle errors!
Use the pipe's CloseWithError function to propagate encoding errors back to error returned from http.Post.
Handle the error returned from http.Post
Close the response body.
Here's the code:
r, w := io.Pipe()
go func() {
w.CloseWithError(json.NewEncoder(w).Encode(data))
}()
// Ensure that read side of pipe is closed. This
// unblocks goroutine in scenario where http.Post
// errors out before reading the entire request body.
defer r.Close()
resp, err := http.Post(url, r)
if err != nil {
// Adjust error handling here to meet application requrirements.
log.Fatal(err)
}
defer resp.Body.Close()
// Use the response here.
If you have a lot of data to send, you can use a pipe:
package main
import (
"encoding/json"
"io"
"net/http"
)
func main() {
m := map[string]int{"SNG_ID": 75498415}
r, w := io.Pipe()
go func() {
json.NewEncoder(w).Encode(m)
w.Close()
}()
http.Post("https://stackoverflow.com", "application/json", r)
}
https://golang.org/pkg/io#Pipe
if you want to do it like that, you need to use this map for unmarshalling json string.
var data map[string]interface{}
but if you need to change the json each time and to make initialization of your requst body more convenient, you can use this map for creating json body.
var bodyJsonMap map[string]interface{}{
"key1": val1,
"key2": val2,
...
}
Then marshal it to a json-string.