Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

179 lines
4.9KB

  1. package main
  2. import (
  3. "biukop/sfm/loan"
  4. "database/sql"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/brianvoe/gofakeit/v6"
  8. log "github.com/sirupsen/logrus"
  9. "net/http"
  10. "net/http/httputil"
  11. "strconv"
  12. "strings"
  13. "time"
  14. )
  15. const apiV1Prefix = "/api/v1/"
  16. type apiV1HandlerMap struct {
  17. Method string
  18. Path string //regex
  19. Handler func(http.ResponseWriter, *http.Request, *loan.Session)
  20. }
  21. var apiV1Handler = []apiV1HandlerMap{
  22. {"POST", "login", apiV1Login},
  23. {"GET", "login", apiV1DumpRequest},
  24. }
  25. //apiV1Main version 1 main entry for all REST API
  26. //
  27. func apiV1Main(w http.ResponseWriter, r *http.Request) {
  28. logRequestDebug(httputil.DumpRequest(r, true))
  29. w.Header().Set("Content-Type", "application/json;charset=UTF-8")
  30. //track browser, and take session from cookie
  31. session := loan.Session{}
  32. apiV1InitSessionByBrowserId(w, r, &session)
  33. //try session login first, if not an empty session will be created
  34. e := apiV1InitSessionByHttpHeader(r, &session)
  35. if e != nil {
  36. log.Warnf("Fail to InitSession %+v", session)
  37. apiV1Client403Error(w, r)
  38. return
  39. }
  40. session.RenewIfExpireSoon()
  41. session.SetRemote(r) //make sure they are using latest remote
  42. session.Add("mid", apiV1GetMachineId(r)) //set machine id
  43. apiV1AddTrackingCookie(w, r, &session) // add tracking cookie to client
  44. //we have a session now, either guest or valid user
  45. //search through handler
  46. path := r.URL.Path[len(apiV1Prefix):] //strip API prefix
  47. for _, node := range apiV1Handler {
  48. if r.Method == node.Method && path == node.Path {
  49. node.Handler(w, r, &session)
  50. e = session.Write() //finish this session to DB
  51. if e != nil {
  52. log.Warnf("Failed to Save Session %+v \n reason \n%s\n", session, e.Error())
  53. }
  54. return
  55. }
  56. }
  57. //Catch for all
  58. e = session.Write() //finish this session to DB
  59. apiV1DumpRequest(w, r, &session)
  60. }
  61. func apiV1GetMachineId(r *http.Request) string {
  62. var mid string
  63. inCookie, e := r.Cookie("mid")
  64. if e == nil {
  65. mid = inCookie.Value
  66. } else {
  67. mid = strconv.Itoa(int(time.Now().Unix())) + "-" + gofakeit.UUID()
  68. }
  69. return mid
  70. }
  71. func apiV1InitSessionByBrowserId(w http.ResponseWriter, r *http.Request, session *loan.Session) {
  72. mid := apiV1GetMachineId(r)
  73. var sid string
  74. inCookie, e := r.Cookie("session")
  75. if e == nil {
  76. sid = inCookie.Value
  77. if sid != "" {
  78. e = session.Read(sid)
  79. if e == nil {
  80. if mid != session.Get("mid") {
  81. session.MarkEmpty()
  82. }
  83. }
  84. } else { //create a new session
  85. session.InitGuest(time.Now().Add(loan.DefaultSessionDuration))
  86. session.Add("mid", mid)
  87. }
  88. }
  89. }
  90. func apiV1AddTrackingCookie(w http.ResponseWriter, r *http.Request, session *loan.Session) {
  91. //add tracking cookie
  92. expiration := time.Now().Add(365 * 24 * time.Hour)
  93. cookie := http.Cookie{Name: "session", Value: session.Id, Expires: expiration}
  94. http.SetCookie(w, &cookie)
  95. mid := apiV1GetMachineId(r)
  96. cookie = http.Cookie{Name: "mid", Value: mid, Expires: expiration}
  97. http.SetCookie(w, &cookie)
  98. }
  99. func apiV1InitSessionByHttpHeader(r *http.Request, ss *loan.Session) (e error) {
  100. sid := r.Header.Get("Biukop-Session")
  101. //make sure session id is given
  102. if sid != "" {
  103. //Try to retrieve a copy of DB session
  104. trial := loan.Session{}
  105. e = trial.Retrieve(r)
  106. if e == nil { //we got existing session from DB
  107. e = trial.ValidateRequest(r)
  108. if e != nil { // db session does not match request
  109. log.Warnf("failed session login %+v, %s", ss, time.Now().Format(time.RFC1123))
  110. ss.InitGuest(time.Now().Add(loan.DefaultSessionDuration))
  111. e = nil
  112. } else { //else, we have logged this user
  113. *ss = trial //overwrite the incoming session
  114. }
  115. } else if e == sql.ErrNoRows { //db does not have required session.
  116. log.Warn("DB has no corresponding session ", sid)
  117. } else { // retrieve has error
  118. log.Warnf("Retrieve Session %s encountered error %s", sid, e.Error())
  119. }
  120. }
  121. //if there is not session incoming
  122. if ss.IsEmpty() { //cookie may have initialized a session
  123. ss.InitGuest(time.Now().Add(loan.DefaultSessionDuration))
  124. e = nil //we try to init an empty one
  125. }
  126. return
  127. }
  128. func apiV1ErrorCheck(e error) {
  129. if nil != e {
  130. panic(e.Error()) //TODO: detailed error check, truck all caller
  131. }
  132. }
  133. func apiV1Server500Error(w http.ResponseWriter, r *http.Request) {
  134. w.WriteHeader(500)
  135. fmt.Fprintf(w, "Server Internal Error "+time.Now().Format(time.RFC1123))
  136. //write log
  137. dump := logRequestDebug(httputil.DumpRequest(r, true))
  138. dump = strings.TrimSpace(dump)
  139. log.Warnf("Unhandled Protocol = %s path= %s", r.Method, r.URL.Path)
  140. }
  141. func apiV1Client403Error(w http.ResponseWriter, r *http.Request) {
  142. w.WriteHeader(403)
  143. type struct403 struct {
  144. Error int
  145. ErrorMsg string
  146. }
  147. e403 := struct403{Error: 403, ErrorMsg: "Not Authorized " + time.Now().Format(time.RFC1123)}
  148. msg403, _ := json.Marshal(e403)
  149. fmt.Fprintln(w, string(msg403))
  150. //write log
  151. dump := logRequestDebug(httputil.DumpRequest(r, true))
  152. dump = strings.TrimSpace(dump)
  153. log.Warnf("Not authorized http(%s) path= %s, %s", r.Method, r.URL.Path, dump)
  154. }