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 metadata提取的PDF元数据,如何转换为一个Excel
  • ¥15 关于arduino编程toCharArray()函数的使用
  • ¥100 vc++混合CEF采用CLR方式编译报错
  • ¥15 coze 的插件输入飞书多维表格 app_token 后一直显示错误,如何解决?
  • ¥15 vite+vue3+plyr播放本地public文件夹下视频无法加载
  • ¥15 c#逐行读取txt文本,但是每一行里面数据之间空格数量不同
  • ¥50 如何openEuler 22.03上安装配置drbd
  • ¥20 ING91680C BLE5.3 芯片怎么实现串口收发数据
  • ¥15 无线连接树莓派,无法执行update,如何解决?(相关搜索:软件下载)
  • ¥15 Windows11, backspace, enter, space键失灵