example_service_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2018 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package sql_test
  5. import (
  6. "context"
  7. "database/sql"
  8. "encoding/json"
  9. "fmt"
  10. "io"
  11. "log"
  12. "net/http"
  13. "time"
  14. )
  15. func Example_openDBService() {
  16. // Opening a driver typically will not attempt to connect to the database.
  17. db, err := sql.Open("driver-name", "database=test1")
  18. if err != nil {
  19. // This will not be a connection error, but a DSN parse error or
  20. // another initialization error.
  21. log.Fatal(err)
  22. }
  23. db.SetConnMaxLifetime(0)
  24. db.SetMaxIdleConns(50)
  25. db.SetMaxOpenConns(50)
  26. s := &Service{db: db}
  27. http.ListenAndServe(":8080", s)
  28. }
  29. type Service struct {
  30. db *sql.DB
  31. }
  32. func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  33. db := s.db
  34. switch r.URL.Path {
  35. default:
  36. http.Error(w, "not found", http.StatusNotFound)
  37. return
  38. case "/healthz":
  39. ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
  40. defer cancel()
  41. err := s.db.PingContext(ctx)
  42. if err != nil {
  43. http.Error(w, fmt.Sprintf("db down: %v", err), http.StatusFailedDependency)
  44. return
  45. }
  46. w.WriteHeader(http.StatusOK)
  47. return
  48. case "/quick-action":
  49. // This is a short SELECT. Use the request context as the base of
  50. // the context timeout.
  51. ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
  52. defer cancel()
  53. id := 5
  54. org := 10
  55. var name string
  56. err := db.QueryRowContext(ctx, `
  57. select
  58. p.name
  59. from
  60. people as p
  61. join organization as o on p.organization = o.id
  62. where
  63. p.id = :id
  64. and o.id = :org
  65. ;`,
  66. sql.Named("id", id),
  67. sql.Named("org", org),
  68. ).Scan(&name)
  69. if err != nil {
  70. if err == sql.ErrNoRows {
  71. http.Error(w, "not found", http.StatusNotFound)
  72. return
  73. }
  74. http.Error(w, err.Error(), http.StatusInternalServerError)
  75. return
  76. }
  77. io.WriteString(w, name)
  78. return
  79. case "/long-action":
  80. // This is a long SELECT. Use the request context as the base of
  81. // the context timeout, but give it some time to finish. If
  82. // the client cancels before the query is done the query will also
  83. // be canceled.
  84. ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
  85. defer cancel()
  86. var names []string
  87. rows, err := db.QueryContext(ctx, "select p.name from people as p where p.active = true;")
  88. if err != nil {
  89. http.Error(w, err.Error(), http.StatusInternalServerError)
  90. return
  91. }
  92. for rows.Next() {
  93. var name string
  94. err = rows.Scan(&name)
  95. if err != nil {
  96. break
  97. }
  98. names = append(names, name)
  99. }
  100. // Check for errors during rows "Close".
  101. // This may be more important if multiple statements are executed
  102. // in a single batch and rows were written as well as read.
  103. if closeErr := rows.Close(); closeErr != nil {
  104. http.Error(w, closeErr.Error(), http.StatusInternalServerError)
  105. return
  106. }
  107. // Check for row scan error.
  108. if err != nil {
  109. http.Error(w, err.Error(), http.StatusInternalServerError)
  110. return
  111. }
  112. // Check for errors during row iteration.
  113. if err = rows.Err(); err != nil {
  114. http.Error(w, err.Error(), http.StatusInternalServerError)
  115. return
  116. }
  117. json.NewEncoder(w).Encode(names)
  118. return
  119. case "/async-action":
  120. // This action has side effects that we want to preserve
  121. // even if the client cancels the HTTP request part way through.
  122. // For this we do not use the http request context as a base for
  123. // the timeout.
  124. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  125. defer cancel()
  126. var orderRef = "ABC123"
  127. tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
  128. _, err = tx.ExecContext(ctx, "stored_proc_name", orderRef)
  129. if err != nil {
  130. tx.Rollback()
  131. http.Error(w, err.Error(), http.StatusInternalServerError)
  132. return
  133. }
  134. err = tx.Commit()
  135. if err != nil {
  136. http.Error(w, "action in unknown state, check state before attempting again", http.StatusInternalServerError)
  137. return
  138. }
  139. w.WriteHeader(http.StatusOK)
  140. return
  141. }
  142. }