goyave.dev/goyave/v4@v4.4.11/request.go (about) 1 package goyave 2 3 import ( 4 "fmt" 5 "net" 6 "net/http" 7 "net/url" 8 "strings" 9 "time" 10 11 "github.com/imdario/mergo" 12 "goyave.dev/goyave/v4/cors" 13 "goyave.dev/goyave/v4/util/fsutil" 14 15 "github.com/google/uuid" 16 "goyave.dev/goyave/v4/validation" 17 ) 18 19 // Request struct represents an http request. 20 // Contains the validated body in the Data attribute if the route was defined with a request generator function 21 type Request struct { 22 httpRequest *http.Request 23 corsOptions *cors.Options 24 route *Route 25 Rules *validation.Rules 26 Params map[string]string 27 Data map[string]interface{} 28 Extra map[string]interface{} 29 User interface{} 30 Lang string 31 cookies []*http.Cookie 32 } 33 34 // Request return the raw http request. 35 // Prefer using the "goyave.Request" accessors. 36 func (r *Request) Request() *http.Request { 37 return r.httpRequest 38 } 39 40 // Method specifies the HTTP method (GET, POST, PUT, etc.). 41 func (r *Request) Method() string { 42 return r.httpRequest.Method 43 } 44 45 // Protocol the protocol used by this request, "HTTP/1.1" for example. 46 func (r *Request) Protocol() string { 47 return r.httpRequest.Proto 48 } 49 50 // URI specifies the URI being requested. 51 // Use this if you absolutely need the raw query params, url, etc. 52 // Otherwise use the provided methods and fields of the "goyave.Request". 53 func (r *Request) URI() *url.URL { 54 return r.httpRequest.URL 55 } 56 57 // Route returns the current route. 58 func (r *Request) Route() *Route { 59 return r.route 60 } 61 62 // Header contains the request header fields either received 63 // by the server or to be sent by the client. 64 // Header names are case-insensitive. 65 // 66 // If the raw request has the following header lines, 67 // 68 // Host: example.com 69 // accept-encoding: gzip, deflate 70 // Accept-Language: en-us 71 // fOO: Bar 72 // foo: two 73 // 74 // then the header map will look like this: 75 // 76 // Header = map[string][]string{ 77 // "Accept-Encoding": {"gzip, deflate"}, 78 // "Accept-Language": {"en-us"}, 79 // "Foo": {"Bar", "two"}, 80 // } 81 func (r *Request) Header() http.Header { 82 return r.httpRequest.Header 83 } 84 85 // ContentLength records the length of the associated content. 86 // The value -1 indicates that the length is unknown. 87 func (r *Request) ContentLength() int64 { 88 return r.httpRequest.ContentLength 89 } 90 91 // RemoteAddress allows to record the network address that 92 // sent the request, usually for logging. 93 func (r *Request) RemoteAddress() string { 94 return r.httpRequest.RemoteAddr 95 } 96 97 // Cookies returns the HTTP cookies sent with the request. 98 func (r *Request) Cookies() []*http.Cookie { 99 if r.cookies == nil { 100 r.cookies = r.httpRequest.Cookies() 101 } 102 return r.cookies 103 } 104 105 // Referrer returns the referring URL, if sent in the request. 106 func (r *Request) Referrer() string { 107 return r.httpRequest.Referer() 108 } 109 110 // UserAgent returns the client's User-Agent, if sent in the request. 111 func (r *Request) UserAgent() string { 112 return r.httpRequest.UserAgent() 113 } 114 115 // BasicAuth returns the username and password provided in the request's 116 // Authorization header, if the request uses HTTP Basic Authentication. 117 func (r *Request) BasicAuth() (username, password string, ok bool) { 118 return r.httpRequest.BasicAuth() 119 } 120 121 // BearerToken extract the auth token from the "Authorization" header. 122 // Only takes tokens of type "Bearer". 123 // Returns empty string if no token found or the header is invalid. 124 func (r *Request) BearerToken() (string, bool) { 125 const schema = "Bearer " 126 header := r.Header().Get("Authorization") 127 if !strings.HasPrefix(header, schema) { 128 return "", false 129 } 130 return strings.TrimSpace(header[len(schema):]), true 131 } 132 133 // CORSOptions returns the CORS options applied to this request, or nil. 134 // The returned object is a copy of the options applied to the router. 135 // Therefore, altering the returned object will not alter the router's options. 136 func (r *Request) CORSOptions() *cors.Options { 137 if r.corsOptions == nil { 138 return nil 139 } 140 141 cpy := *r.corsOptions 142 return &cpy 143 } 144 145 // Has check if the given field exists in the request data. 146 func (r *Request) Has(field string) bool { 147 _, exists := r.Data[field] 148 return exists 149 } 150 151 // String get a string field from the request data. 152 // Panics if the field is not a string. 153 func (r *Request) String(field string) string { 154 str, ok := r.Data[field].(string) 155 if !ok { 156 panic(fmt.Sprintf("Field \"%s\" is not a string", field)) 157 } 158 return str 159 } 160 161 // Numeric get a numeric field from the request data. 162 // Panics if the field is not numeric. 163 func (r *Request) Numeric(field string) float64 { 164 str, ok := r.Data[field].(float64) 165 if !ok { 166 panic(fmt.Sprintf("Field \"%s\" is not numeric", field)) 167 } 168 return str 169 } 170 171 // Integer get an integer field from the request data. 172 // Panics if the field is not an integer. 173 func (r *Request) Integer(field string) int { 174 str, ok := r.Data[field].(int) 175 if !ok { 176 panic(fmt.Sprintf("Field \"%s\" is not an integer", field)) 177 } 178 return str 179 } 180 181 // Bool get a bool field from the request data. 182 // Panics if the field is not a bool. 183 func (r *Request) Bool(field string) bool { 184 str, ok := r.Data[field].(bool) 185 if !ok { 186 panic(fmt.Sprintf("Field \"%s\" is not a bool", field)) 187 } 188 return str 189 } 190 191 // File get a file field from the request data. 192 // Panics if the field is not numeric. 193 func (r *Request) File(field string) []fsutil.File { 194 str, ok := r.Data[field].([]fsutil.File) 195 if !ok { 196 panic(fmt.Sprintf("Field \"%s\" is not a file", field)) 197 } 198 return str 199 } 200 201 // Timezone get a timezone field from the request data. 202 // Panics if the field is not a timezone. 203 func (r *Request) Timezone(field string) *time.Location { 204 str, ok := r.Data[field].(*time.Location) 205 if !ok { 206 panic(fmt.Sprintf("Field \"%s\" is not a timezone", field)) 207 } 208 return str 209 } 210 211 // IP get an IP field from the request data. 212 // Panics if the field is not an IP. 213 func (r *Request) IP(field string) net.IP { 214 str, ok := r.Data[field].(net.IP) 215 if !ok { 216 panic(fmt.Sprintf("Field \"%s\" is not an IP", field)) 217 } 218 return str 219 } 220 221 // URL get a URL field from the request data. 222 // Panics if the field is not a URL. 223 func (r *Request) URL(field string) *url.URL { 224 str, ok := r.Data[field].(*url.URL) 225 if !ok { 226 panic(fmt.Sprintf("Field \"%s\" is not a URL", field)) 227 } 228 return str 229 } 230 231 // UUID get a UUID field from the request data. 232 // Panics if the field is not a UUID. 233 func (r *Request) UUID(field string) uuid.UUID { 234 str, ok := r.Data[field].(uuid.UUID) 235 if !ok { 236 panic(fmt.Sprintf("Field \"%s\" is not an UUID", field)) 237 } 238 return str 239 } 240 241 // Date get a date field from the request data. 242 // Panics if the field is not a date. 243 func (r *Request) Date(field string) time.Time { 244 str, ok := r.Data[field].(time.Time) 245 if !ok { 246 panic(fmt.Sprintf("Field \"%s\" is not a date", field)) 247 } 248 return str 249 } 250 251 // Object get an object field from the request data. 252 // Panics if the field is not an object. 253 func (r *Request) Object(field string) map[string]interface{} { 254 str, ok := r.Data[field].(map[string]interface{}) 255 if !ok { 256 panic(fmt.Sprintf("Field \"%s\" is not an object", field)) 257 } 258 return str 259 } 260 261 // ToStruct map the request data to a struct. 262 // 263 // type UserInsertRequest struct { 264 // Username string 265 // Email string 266 // } 267 // //... 268 // userInsertRequest := UserInsertRequest{} 269 // if err := request.ToStruct(&userInsertRequest); err != nil { 270 // panic(err) 271 // } 272 func (r *Request) ToStruct(dst interface{}) error { 273 return mergo.Map(dst, r.Data) 274 } 275 276 func (r *Request) validate() validation.Errors { 277 if r.Rules == nil { 278 return nil 279 } 280 281 extra := map[string]interface{}{ 282 "request": r, 283 } 284 contentType := r.httpRequest.Header.Get("Content-Type") 285 return validation.ValidateWithExtra(r.Data, r.Rules, strings.HasPrefix(contentType, "application/json"), r.Lang, extra) 286 }