requestid.go (2552B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package middleware 19 20 import ( 21 "bufio" 22 "crypto/rand" 23 "encoding/base32" 24 "encoding/binary" 25 "io" 26 "sync" 27 "time" 28 29 "github.com/gin-gonic/gin" 30 "github.com/superseriousbusiness/gotosocial/internal/gtscontext" 31 ) 32 33 var ( 34 // crand provides buffered reads of random input. 35 crand = bufio.NewReader(rand.Reader) 36 mrand sync.Mutex 37 38 // base32enc is a base 32 encoding based on a human-readable character set (no padding). 39 base32enc = base32.NewEncoding("0123456789abcdefghjkmnpqrstvwxyz").WithPadding(-1) 40 ) 41 42 // generateID generates a new ID string. 43 func generateID() string { 44 // 0:8 = timestamp 45 // 8:12 = entropy 46 // 47 // inspired by ULID. 48 b := make([]byte, 12) 49 50 // Get current time in milliseconds. 51 ms := uint64(time.Now().UnixMilli()) 52 53 // Store binary time data in byte buffer. 54 binary.LittleEndian.PutUint64(b[0:8], ms) 55 56 mrand.Lock() 57 // Read random bits into buffer end. 58 _, _ = io.ReadFull(crand, b[8:12]) 59 mrand.Unlock() 60 61 // Encode the binary time+entropy ID. 62 return base32enc.EncodeToString(b) 63 } 64 65 // AddRequestID returns a gin middleware which adds a unique ID to each request (both response header and context). 66 func AddRequestID(header string) gin.HandlerFunc { 67 return func(c *gin.Context) { 68 id := c.GetHeader(header) 69 // Have we found anything? 70 if id == "" { 71 // Generate new ID. 72 // 73 // 0:8 = timestamp 74 // 8:12 = entropy 75 id = generateID() 76 // Set the request ID in the req header in case we pass the request along 77 // to another service 78 c.Request.Header.Set(header, id) 79 } 80 81 // Store request ID in new request context and set on gin ctx. 82 ctx := gtscontext.SetRequestID(c.Request.Context(), id) 83 c.Request = c.Request.WithContext(ctx) 84 85 // Set the request ID in the rsp header. 86 c.Writer.Header().Set(header, id) 87 } 88 }