dtz88967 2019-03-07 22:17
浏览 89
已采纳

使用PostgreSQL和json-api的Post Request返回一个空的正文

After a POST request, i was expecting to have a last inserted record marshalled into json, but instead returns an empty body. What am i not doing well?

package models


import (
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    "log"
    "net/http"
    "strconv"

    "github.com/go-chi/chi"
    "github.com/google/jsonapi"
    "github.com/thedevsaddam/renderer"
    "github.com/xo/dburl"
)

var rnd = renderer.New()

var flagVerbose = flag.Bool("v", false, "verbose")

var FlagURL = flag.String("url", "postgres://postgres:@127.0.0.1/sweb", "url")

// Page represents a row from 'public.pages'.
type Page struct {
    Tag   string `jsonapi:"attr,tag"`      // tag
    Body  string `jsonapi:"attr,body"`     // body
    Slug  string `jsonapi:"attr,slug"`     // slug
    Title string `jsonapi:"attr,title"`    // title
    ID    int    `jsonapi:"primary,pages"` // id
    Link  string `jsonapi:"attr,link"`     // link

    // xo fields
    _exists, _deleted bool
}

func (page Page) JSONAPILinks() *jsonapi.Links {
    return &jsonapi.Links{
        "self": fmt.Sprintf("https://%d", page.ID),
    }
}

I believe this is the culprit. After inserting a record, it should return the last inserted record as specified.

func (p *Page) PInsert(db XODB) (*Page, error) {
    var err error

    // if already exist, bail
    if p._exists {
        return p, errors.New("insert failed: already exists")
    }

    // sql insert query, primary key provided by sequence
    const sqlstr = `INSERT INTO public.pages (` +
        `tag, body, slug, title` +
        `) VALUES (` +
        `$1, $2, $3, $4` +
        `) RETURNING id, tag, body, title`

    // run query
    XOLog(sqlstr, p.Tag, p.Body, p.Slug, p.Title)
    err = db.QueryRow(sqlstr, p.Tag, p.Body, p.Slug, p.Title).Scan(&p.ID, &p.Tag, &p.Body, &p.Title)
    if err != nil {
        return p, err
    }

    // set existence
    p._exists = true

    return p, nil
}

Update updates the Page in the database and return last inserted records. The same should apply for the Update function

func (p *Page) Update(db XODB) (*Page, error) {
    var err error

    // if doesn't exist, bail
    if !p._exists {
        return p, errors.New("update failed: does not exist")
    }

    // if deleted, bail
    if p._deleted {
        return p, errors.New("update failed: marked for deletion")
    }

    // sql query
    const sqlstr = `UPDATE public.pages SET (` +
        `tag, body, slug, title` +
        `) = ( ` +
        `$1, $2, $3, $4` +
        `) WHERE id = $5`

    // run query
    XOLog(sqlstr, p.Tag, p.Body, p.Slug, p.Title, p.ID)
    _, err = db.Exec(sqlstr, p.Tag, p.Body, p.Slug, p.Title, p.ID)
    return p, err
}

func (p *Page) PSave(db XODB) (*Page, error) {
    if p.Exists() {
        return p.Update(db)
    }

    return p.PInsert(db)
}


func NewPage(w http.ResponseWriter, r *http.Request) {

    db, err := dburl.Open(*FlagURL)
    defer db.Close()
    if err != nil {
        log.Fatal(err)
    }

    var page Page

    //page := new(Page)

    if err := jsonapi.UnmarshalPayload(r.Body, &page); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }


    p, err := page.PSave(db)
    if err != nil {
        fmt.Println(err)
        if err := jsonapi.MarshalPayload(w, p); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            fmt.Println(err)
        }


    }


    w.Header().Set("Content-Type", jsonapi.MediaType)
    w.WriteHeader(http.StatusCreated)

}

This is the last function i believe the issue is happening from. the last inserted record supposed to be marshalled into json.

  • 写回答

1条回答 默认 最新

  • doujie4050 2019-03-08 07:39
    关注

    Your last section of code contains a number of mistakes. The relevant section (without the useless and obfuscating Printlns) is:

    p, err := page.PSave(db)
    if err != nil {
        if err := jsonapi.MarshalPayload(w, p); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    }
    
    w.Header().Set("Content-Type", jsonapi.MediaType)
    w.WriteHeader(http.StatusCreated)
    

    And the primary mistake is that json.MarshalPayload is only called when err != nil. In other words, you only serialize the page if you failed to save it.

    The secondary mistake is that jsonapi.MarshalPayload will call Write on the http.ResponseWriter. This turns all subsequent calls to Header().Set and WriteHeader into no-ops.

    More correct code would look like this.

    // 1. Save the page in the database, bail on error
    p, err := page.PSave(db)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return 
    }
    
    // 2. Marshal the page into an intermediate buffer, bail on error
    var buf bytes.Buffer
    if err := jsonapi.MarshalPayload(&buf, p); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return 
    }
    
    // 3. Write the entire response; failures to write the intermediate buffer
    // cannot be communicated over HTTP
    w.Header().Set("Content-Type", jsonapi.MediaType)
    w.WriteHeader(http.StatusCreated)
    if _, err := buf.WriteTo(w); err != nil {
        log.Printf("failed to write response: %v", err)
        return 
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 c语言怎么用printf(“\b \b”)与getch()实现黑框里写入与删除?
  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?
  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?