requestid.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package requestid
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "net/http/httputil"
  6. "github.com/kataras/iris/v12/context"
  7. "github.com/google/uuid"
  8. )
  9. func init() {
  10. context.SetHandlerName("iris/middleware/requestid.*", "iris.request.id")
  11. }
  12. const xRequestIDHeaderKey = "X-Request-Id"
  13. // Generator defines the function which should extract or generate
  14. // a Request ID. See `DefaultGenerator` and `New` package-level functions.
  15. type Generator func(ctx *context.Context) string
  16. // DefaultGenerator is the default `Generator` that is used
  17. // when nil is passed on `New` package-level function.
  18. // It extracts the ID from the "X-Request-ID" request header value
  19. // or, if missing, it generates a new UUID(v4) and sets the header and context value.
  20. //
  21. // See `Get` package-level function too.
  22. var DefaultGenerator Generator = func(ctx *context.Context) string {
  23. id := ctx.ResponseWriter().Header().Get(xRequestIDHeaderKey)
  24. if id != "" {
  25. return id
  26. }
  27. id = ctx.GetHeader(xRequestIDHeaderKey)
  28. if id == "" {
  29. uid, err := uuid.NewRandom()
  30. if err != nil {
  31. ctx.StopWithStatus(500)
  32. return ""
  33. }
  34. id = uid.String()
  35. }
  36. ctx.Header(xRequestIDHeaderKey, id)
  37. return id
  38. }
  39. // HashGenerator uses the request's hash to generate a fixed-length Request ID.
  40. // Note that one or many requests may contain the same ID, so it's not unique.
  41. func HashGenerator(includeBody bool) Generator {
  42. return func(ctx *context.Context) string {
  43. ctx.Header(xRequestIDHeaderKey, Hash(ctx, includeBody))
  44. return DefaultGenerator(ctx)
  45. }
  46. }
  47. // New returns a new request id middleware.
  48. // It optionally accepts an ID Generator.
  49. // The Generator can stop the handlers chain with an error or
  50. // return a valid ID (string).
  51. // If it's nil then the `DefaultGenerator` will be used instead.
  52. func New(generator ...Generator) context.Handler {
  53. gen := DefaultGenerator
  54. if len(generator) > 0 {
  55. gen = generator[0]
  56. }
  57. return func(ctx *context.Context) {
  58. if Get(ctx) != "" {
  59. ctx.Next()
  60. return
  61. }
  62. id := gen(ctx)
  63. if ctx.IsStopped() {
  64. // ctx.Next checks that
  65. // but we don't want to call SetID if generator failed.
  66. return
  67. }
  68. ctx.SetID(id)
  69. ctx.Next()
  70. }
  71. }
  72. // Get returns the Request ID or empty string.
  73. //
  74. // A shortcut of `context.GetID().(string)`.
  75. func Get(ctx *context.Context) string {
  76. v := ctx.GetID()
  77. if v != nil {
  78. if id, ok := v.(string); ok {
  79. return id
  80. }
  81. }
  82. return ""
  83. }
  84. // Hash returns the sha1 hash of the request.
  85. // It does not capture error, instead it returns an empty string.
  86. func Hash(ctx *context.Context, includeBody bool) string {
  87. h := sha256.New() // sha1 fits here as well.
  88. b, err := httputil.DumpRequest(ctx.Request(), includeBody)
  89. if err != nil {
  90. return ""
  91. }
  92. h.Write(b)
  93. return hex.EncodeToString(h.Sum(nil))
  94. }