<p>I've written a simple REST API deployed on AWS elasticbeanstalk that talks to an AWS RDS. </p>
<pre><code>package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
type Message struct {
ID int `json:"id"`
Body string `json:"message_body"`
Recipient string `json:"message_recipient"`
}
var (
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
os.Getenv("RDS_USERNAME"),
os.Getenv("RDS_PASSWORD"),
os.Getenv("RDS_HOSTNAME"),
os.Getenv("RDS_PORT"),
"test_msg")
getAllMessagesQuery = "SELECT id, message_body, message_recipient FROM message_tbl"
getMessageByIDQuery = "SELECT id, message_body, message_recipient FROM message_tbl WHERE id=?"
postMessageQuery = "INSERT INTO message_tbl(message_body, message_recipient) VALUES(?,?)"
db *sql.DB
)
func main() {
db, _ = sql.Open("mysql", dataSourceName)
defer db.Close()
router := mux.NewRouter()
router.StrictSlash(true)
router.HandleFunc("/message", GetAllMessages).Methods("GET")
router.HandleFunc("/message/{id}", GetMessageByID).Methods("GET")
router.HandleFunc("/message", PostMessage).Methods("POST")
log.Fatal(http.ListenAndServe(":5000", router))
}
func PostMessage(w http.ResponseWriter, req *http.Request) {
decoder := json.NewDecoder(req.Body)
var m Message
err := decoder.Decode(&m)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
if m.Body == "" || m.Recipient == "" {
invalid := struct {
ErrorMessage string `json:"error_message"`
}{
"Invalid parameters",
}
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(invalid)
return
}
res, _ := db.Exec(postMessageQuery, m.Body, m.Recipient)
lastID, _ := res.LastInsertId()
returnID := struct {
LastID int64 `json:"message_id"`
}{
lastID,
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(returnID)
}
func GetAllMessages(w http.ResponseWriter, req *http.Request) {
var messages []Message
rows, _ := db.Query(getAllMessagesQuery)
defer rows.Close()
for rows.Next() {
var m Message
rows.Scan(&m.ID, &m.Body, &m.Recipient)
messages = append(messages, m)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(messages)
}
func GetMessageByID(w http.ResponseWriter, req *http.Request) {
params := mux.Vars(req)
messageID := params["id"]
w.Header().Set("Content-Type", "application/json")
var m Message
err := db.QueryRow(getMessageByIDQuery, messageID).Scan(&m.ID, &m.Body, &m.Recipient)
if err == sql.ErrNoRows {
empty := struct {
ErrorMessage string `json:"error_message"`
}{
"No such message",
}
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(empty)
return
}
json.NewEncoder(w).Encode(m)
}
</code></pre>
<p>What's the best way to secure this API? Should I use JWT? Whats the recommended framework/toolkit for web APIs? I know I can use the stdlib, but writing this basic API using only net/http requires lots of boilerplate. </p>
<hr/>**评论:**<br/><br/>whizack: <pre><ul>
<li><p>you should have a consistent error handling struct that includes request data. defining anon structs for every error isn't ideal</p></li>
<li><p>if you're not doing streaming json, you should use unmarshal/marshal instead</p></li>
<li><p>consider wrapping the response writer with methods like Respond(interface{}) error, Header(key, value string), and Status(int) so your code reads like what you're actually doing rather than like raw http code.</p></li>
<li><p>sanitize your input data. message/{id} can not possibly be valid for any value but an int64 value, but you basically allow anything to be specified</p></li>
<li><p>don't panic in your handlers. sending invalid json to POST /message will crash your service</p></li>
<li><p>GET /message is unbounded, appends to a zero sized array (requiring substantial growth over result size), and doesn't support range queries. Once this db has a large number of messages the query will cost an insane amount of time/memory to aggregate the results per request</p></li>
<li><p>DSN is invalid if environment variables aren't set. You should be providing safe defaults for each variable (assuming a localhost db instance for example)</p></li>
<li><p>You should provide handlers for unspecified routes, etc. that return valid json responses and status codes.</p></li>
</ul></pre>gohacker: <pre><blockquote>
<p>if you're not doing streaming json, you should use unmarshal/marshal instead</p>
</blockquote>
<p>json.Marshal allocates memory for the entire json, which might be huge; json.Encoder writes straight to the response.</p></pre>newbgopher: <pre><p>thanks! i've learned a lot from your comments.</p></pre>lu_nemec: <pre><p>Hi,</p>
<p>1) you don't need to have 1 db.Open() - it doesn't directly create connection, and is goroutine + thread safe, you can call db.Open() directly in the function that needs to do DB operations...</p>
<p>2) no need to call defer db.Close(): <a href="https://golang.org/pkg/database/sql/#DB.Close" rel="nofollow">https://golang.org/pkg/database/sql/#DB.Close</a>. </p>
<p>3) I recommend separating handlers from db operations by writing separate functions for DB - func getMessage(msgId string) error {}. This way the internal API is cleaner and you can split the database operations into separate package.</p>
<p>4) most people use Gorilla toolkit for writing web applications. It is not directly a framework but rather collection of tools. You can't go wrong using it. There are several "frameworks" that do most of the boilerplate, but they are not as extendible - Echo is one of those for example..</p>
<p>About the security - it depends on the usecase. If you have users that need to log-in before read/write operations, JWT are fine, but you have to write client JS code to use it correctly.</p></pre>heraclmene: <pre><p>1) I believe you're incorrect [0] with this statement. </p>
<p>[0]<code>The returned DB is safe for concurrent use by multiple goroutines and maintains its own pool of idle connections. Thus, the Open function should be called just once. It is rarely necessary to close a DB.</code> - <a href="https://golang.org/pkg/database/sql/#Open" rel="nofollow">https://golang.org/pkg/database/sql/#Open</a></p></pre>lu_nemec: <pre><p>Ah, thanks! I somehow missed this ... oh god, this happened with http.Client not returning error on non-2xx statuses as well :D gotta pay more attention to the docs! :D</p></pre>newbgopher: <pre><p>Thanks for the response! Any opinions about go-kit? </p></pre>lu_nemec: <pre><p>Hmm, I haven't heard about it, I'll take a look but it looks fine on first glance.</p>
<p>One more thing, maybe the most important - create a custom http.Server{} instance instead of the default. By default they have all Timeouts set to 0 - infinite, which is not a good idea. You should have something like this:</p>
<pre><code>s := &http.Server{
Addr: ":8080",
Handler: myHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
</code></pre>
<p>You can find more information here: <a href="https://golang.org/pkg/net/http/#pkg-examples" rel="nofollow">https://golang.org/pkg/net/http/#pkg-examples</a></p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传