|
@@ -1,6 +1,7 @@
|
|
|
package main
|
|
|
|
|
|
import (
|
|
|
+ "encoding/json"
|
|
|
"flag"
|
|
|
"fmt"
|
|
|
"html"
|
|
@@ -15,8 +16,6 @@ import (
|
|
|
"strings"
|
|
|
|
|
|
"github.com/go-chi/chi"
|
|
|
- "github.com/jackc/pgx"
|
|
|
- "github.com/tidwall/sjson"
|
|
|
)
|
|
|
|
|
|
const (
|
|
@@ -29,44 +28,23 @@ const (
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
- // Database
|
|
|
- worldSelect = "SELECT id, randomNumber FROM World WHERE id = $1"
|
|
|
- worldUpdate = "UPDATE World SET randomNumber = $1 WHERE id = $2"
|
|
|
- fortuneSelect = "SELECT id, message FROM Fortune;"
|
|
|
- worldRowCount = 10000
|
|
|
- maxConnectionCount = 256
|
|
|
-
|
|
|
helloWorldString = "Hello, World!"
|
|
|
extraFortuneMessage = "Additional fortune added at request time."
|
|
|
)
|
|
|
|
|
|
var (
|
|
|
- connectionString = "postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world?sslmode=disable"
|
|
|
- bindHost = ":8080"
|
|
|
- debugFlag = false
|
|
|
- preforkFlag = false
|
|
|
- childFlag = false
|
|
|
+ bindHost = ":8080"
|
|
|
+ debugFlag = false
|
|
|
+ preforkFlag = false
|
|
|
+ childFlag = false
|
|
|
|
|
|
helloWorldMessage = &Message{helloWorldString}
|
|
|
extraFortune = &Fortune{Message: extraFortuneMessage}
|
|
|
)
|
|
|
|
|
|
-// sjson
|
|
|
-var (
|
|
|
- messageJSONTmpl = []byte(`{"message": ""}`)
|
|
|
- worldJSONTmpl = []byte(`{"id": 0, "randomNumber": 0}`)
|
|
|
- optimistic = &sjson.Options{Optimistic: true}
|
|
|
- inPlace = &sjson.Options{Optimistic: true, ReplaceInPlace: true}
|
|
|
-)
|
|
|
-
|
|
|
// Message is a JSON struct to render a message
|
|
|
type Message struct {
|
|
|
- Message string
|
|
|
-}
|
|
|
-
|
|
|
-// MarshalJSON marshals the object as json
|
|
|
-func (m Message) MarshalJSON() ([]byte, error) {
|
|
|
- return sjson.SetBytesOptions(messageJSONTmpl, "message", m.Message, optimistic)
|
|
|
+ Message string `json:"message"`
|
|
|
}
|
|
|
|
|
|
// World is a JSON struct to render a random number
|
|
@@ -75,30 +53,6 @@ type World struct {
|
|
|
RandomNumber uint16 `json:"randomNumber"`
|
|
|
}
|
|
|
|
|
|
-// MarshalJSON marshals the object as json
|
|
|
-func (w World) MarshalJSON() ([]byte, error) {
|
|
|
- data, _ := sjson.SetBytesOptions(worldJSONTmpl, "id", w.ID, optimistic)
|
|
|
- return sjson.SetBytesOptions(data, "randomNumber", w.RandomNumber, inPlace)
|
|
|
-}
|
|
|
-
|
|
|
-// Worlds is a list of worlds
|
|
|
-type Worlds []World
|
|
|
-
|
|
|
-// MarshalJSON marshals the object as json
|
|
|
-func (ws Worlds) MarshalJSON() ([]byte, error) {
|
|
|
- jsonResult := []byte(`[]`)
|
|
|
-
|
|
|
- for _, w := range ws {
|
|
|
- jsonResult, _ = sjson.SetBytesOptions(jsonResult, "-1", &w, inPlace)
|
|
|
- }
|
|
|
-
|
|
|
- return jsonResult, nil
|
|
|
-}
|
|
|
-
|
|
|
-func randomRow() *pgx.Row {
|
|
|
- return defaultDB.QueryRow("worldSelect", rand.Intn(worldRowCount)+1)
|
|
|
-}
|
|
|
-
|
|
|
// Fortune renders a fortune in JSON
|
|
|
type Fortune struct {
|
|
|
ID uint16 `json:"id"`
|
|
@@ -118,30 +72,31 @@ func (s Fortunes) Swap(i, j int) {
|
|
|
s[i], s[j] = s[j], s[i]
|
|
|
}
|
|
|
|
|
|
+func (s Fortunes) Less(i, j int) bool { return s[i].Message < s[j].Message }
|
|
|
+
|
|
|
// Sets the content type of response. Also adds the Server header.
|
|
|
func setContentType(w http.ResponseWriter, contentType string) {
|
|
|
- w.Header().Set("Server", "Chi")
|
|
|
+ w.Header().Set("Server", "chi")
|
|
|
w.Header().Set("Content-Type", contentType)
|
|
|
}
|
|
|
|
|
|
// Test 1: JSON Serialization
|
|
|
func serializeJSON(w http.ResponseWriter, r *http.Request) {
|
|
|
setContentType(w, "application/json")
|
|
|
-
|
|
|
- data, _ := helloWorldMessage.MarshalJSON()
|
|
|
- _, _ = w.Write(data)
|
|
|
+ _ = json.NewEncoder(w).Encode(helloWorldMessage)
|
|
|
}
|
|
|
|
|
|
// Test 2: Single Database Query
|
|
|
func singleQuery(w http.ResponseWriter, r *http.Request) {
|
|
|
- world := World{}
|
|
|
- if err := randomRow().Scan(&world.ID, &world.RandomNumber); err != nil {
|
|
|
- log.Printf("Error scanning world row: %s", err.Error())
|
|
|
+ var world World
|
|
|
+ err := worldStatement.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world.ID, &world.RandomNumber)
|
|
|
+ if err != nil {
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
setContentType(w, "application/json")
|
|
|
- data, _ := world.MarshalJSON()
|
|
|
- _, _ = w.Write(data)
|
|
|
+ _ = json.NewEncoder(w).Encode(&world)
|
|
|
}
|
|
|
|
|
|
// Caps queries parameter between 1 and 500.
|
|
@@ -163,41 +118,41 @@ func sanitizeQueryParam(queries string) int {
|
|
|
// Test 3: Multiple Database Queries
|
|
|
func multipleQueries(w http.ResponseWriter, r *http.Request) {
|
|
|
queries := sanitizeQueryParam(r.URL.Query().Get("queries"))
|
|
|
- worlds := make(Worlds, queries)
|
|
|
+ worlds := make([]World, queries)
|
|
|
|
|
|
for i := 0; i < queries; i++ {
|
|
|
- if err := randomRow().Scan(&worlds[i].ID, &worlds[i].RandomNumber); err != nil {
|
|
|
- log.Printf("Error scanning world row: %s", err.Error())
|
|
|
+ err := worldStatement.QueryRow(rand.Intn(worldRowCount)+1).Scan(&worlds[i].ID, &worlds[i].RandomNumber)
|
|
|
+ if err != nil {
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
}
|
|
|
}
|
|
|
|
|
|
setContentType(w, "application/json")
|
|
|
- data, _ := worlds.MarshalJSON()
|
|
|
- _, _ = w.Write(data)
|
|
|
+ _ = json.NewEncoder(w).Encode(worlds)
|
|
|
}
|
|
|
|
|
|
// Test 4: Fortunes
|
|
|
func fortunes(w http.ResponseWriter, r *http.Request) {
|
|
|
- rows, err := defaultDB.Query("fortuneSelect")
|
|
|
+ rows, err := fortuneStatement.Query()
|
|
|
if err != nil {
|
|
|
- log.Printf("Error preparing statement: %v", err)
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- fortunes := make(Fortunes, 0, 256)
|
|
|
-
|
|
|
- for rows.Next() {
|
|
|
+ fortunes := make(Fortunes, 0, 16)
|
|
|
+ for rows.Next() { //Fetch rows
|
|
|
fortune := Fortune{}
|
|
|
if err := rows.Scan(&fortune.ID, &fortune.Message); err != nil {
|
|
|
- log.Printf("Error scanning fortune row: %s", err.Error())
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
}
|
|
|
fortunes = append(fortunes, &fortune)
|
|
|
}
|
|
|
- rows.Close()
|
|
|
- fortunes = append(fortunes, extraFortune)
|
|
|
+ fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
|
|
|
+
|
|
|
+ sort.Sort(fortunes)
|
|
|
|
|
|
- sort.Slice(fortunes, func(i, j int) bool {
|
|
|
- return fortunes[i].Message < fortunes[j].Message
|
|
|
- })
|
|
|
setContentType(w, "text/html; charset=utf-8")
|
|
|
|
|
|
var body strings.Builder
|
|
@@ -210,23 +165,22 @@ func fortunes(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
// Test 5: Database Updates
|
|
|
func dbupdate(w http.ResponseWriter, r *http.Request) {
|
|
|
- queries := sanitizeQueryParam(r.URL.Query().Get("queries"))
|
|
|
- worlds := make(Worlds, queries)
|
|
|
-
|
|
|
- for i := 0; i < queries; i++ {
|
|
|
- w := &worlds[i]
|
|
|
- if err := randomRow().Scan(&w.ID, &w.RandomNumber); err != nil {
|
|
|
- log.Printf("Error scanning world row: %s", err.Error())
|
|
|
+ numQueries := sanitizeQueryParam(r.URL.Query().Get("queries"))
|
|
|
+ worlds := make([]World, numQueries)
|
|
|
+ for i := 0; i < numQueries; i++ {
|
|
|
+ if err := worldStatement.QueryRow(rand.Intn(worldRowCount)+1).Scan(&worlds[i].ID, &worlds[i].RandomNumber); err != nil {
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
}
|
|
|
worlds[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
|
|
|
- if _, err := defaultDB.Exec("worldUpdate", w.RandomNumber, w.ID); err != nil {
|
|
|
- log.Printf("Error updating world row: %s", err.Error())
|
|
|
+ if _, err := updateStatement.Exec(worlds[i].RandomNumber, worlds[i].ID); err != nil {
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
}
|
|
|
}
|
|
|
|
|
|
setContentType(w, "application/json")
|
|
|
- data, _ := worlds.MarshalJSON()
|
|
|
- _, _ = w.Write(data)
|
|
|
+ _ = json.NewEncoder(w).Encode(worlds)
|
|
|
}
|
|
|
|
|
|
// Test 6: Plaintext
|
|
@@ -237,7 +191,6 @@ func plaintext(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
func init() {
|
|
|
flag.StringVar(&bindHost, "bind", bindHost, "Set bind host")
|
|
|
- flag.StringVar(&connectionString, "db", connectionString, "Set database URL")
|
|
|
flag.BoolVar(&debugFlag, "debug", false, "Enable debug mode")
|
|
|
flag.BoolVar(&preforkFlag, "prefork", false, "Enable prefork mode")
|
|
|
flag.BoolVar(&childFlag, "child", false, "Enable child mode")
|
|
@@ -274,8 +227,6 @@ func main() {
|
|
|
listener = doPrefork(childFlag, bindHost)
|
|
|
}
|
|
|
|
|
|
- initDBConnection(connectionString)
|
|
|
-
|
|
|
if !debugFlag {
|
|
|
log.SetOutput(ioutil.Discard)
|
|
|
}
|