-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathctx.go
775 lines (696 loc) · 22 KB
/
ctx.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
// Ctx represents the context of an HTTP request and response.
//
// It provides access to the request, response, headers, query parameters,
// body, and other necessary attributes for handling HTTP requests.
//
// Fields:
// - Response: The HTTP response writer.
// - Request: The HTTP request object.
// - resStatus: The HTTP response status code.
// - MoreRequests: Counter for additional requests in a batch processing scenario.
// - bodyByte: The raw body content as a byte slice.
// - JsonStr: The raw body content as a string.
// - Headers: A map containing all request headers.
// - Params: A map containing URL parameters (e.g., /users/:id → id).
// - Query: A map containing query parameters (e.g., ?name=John).
// - uploadFileSize: The maximum allowed upload file size in bytes.
// - App: A reference to the Quick application instance.
package quick
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"os"
"path/filepath"
"strings"
)
// Ctx represents the context of an HTTP request and response.
type Ctx struct {
Response http.ResponseWriter // HTTP response writer to send responses
Request *http.Request // Incoming HTTP request object
resStatus int // HTTP response status code
MoreRequests int // Counter for batch processing requests
bodyByte []byte // Raw request body as byte slice
JsonStr string // Request body as a string (for JSON handling)
Headers map[string][]string // Map of request headers
Params map[string]string // Map of URL parameters
Query map[string]string // Map of query parameters
uploadFileSize int64 // Maximum allowed file upload size (bytes)
App *Quick // Reference to the Quick application instance
}
// SetStatus sets the HTTP response status code.
//
// Parameters:
// - status: The HTTP status code to be set.
func (c *Ctx) SetStatus(status int) {
c.resStatus = status
}
// UploadedFile holds details of an uploaded file.
//
// Fields:
// - File: The uploaded file as a multipart.File.
// - Multipart: The file header containing metadata about the uploaded file.
// - Info: Additional information about the file, including filename, size, and content type.
type UploadedFile struct {
File multipart.File
Multipart *multipart.FileHeader
Info FileInfo
}
// FileInfo contains metadata about an uploaded file.
//
// Fields:
// - Filename: The original name of the uploaded file.
// - Size: The file size in bytes.
// - ContentType: The MIME type of the file (e.g., "image/png").
// - Bytes: The raw file content as a byte slice.
type FileInfo struct {
Filename string
Size int64
ContentType string
Bytes []byte
}
// GetHeader retrieves a specific header value from the request.
//
// Parameters:
// - key: The name of the header to retrieve.
//
// Returns:
// - string: The value of the specified header, or an empty string if not found.
func (c *Ctx) GetHeader(key string) string {
return c.Request.Header.Get(key)
}
// GetHeaders retrieves all headers from the incoming HTTP request.
//
// This method provides direct access to the request headers, allowing
// middleware and handlers to inspect and modify header values.
//
// Example Usage:
//
// q.Get("/", func(c *quick.Ctx) error {
// headers := c.GetHeaders()
// return c.Status(200).JSON(headers)
// })
//
// Returns:
// - http.Header: A map containing all request headers.
func (c *Ctx) GetHeaders() http.Header {
return c.Request.Header
}
// RemoteIP extracts the client's IP address from the request.
//
// If the request's `RemoteAddr` contains a port (e.g., "192.168.1.100:54321"),
// this method extracts only the IP part. If extraction fails, it returns
// the full `RemoteAddr` as a fallback.
//
// Example Usage:
//
// q.Get("/", func(c *quick.Ctx) error {
// return c.Status(200).SendString("Client IP: " + c.RemoteIP())
// })
//
// Returns:
// - string: The client's IP address. If extraction fails, returns `RemoteAddr`.
func (c *Ctx) RemoteIP() string {
ip, _, err := net.SplitHostPort(c.Request.RemoteAddr)
if err != nil {
return c.Request.RemoteAddr
}
return ip
}
// Method retrieves the HTTP method of the current request.
//
// This method returns the HTTP method as a string, such as "GET", "POST", "PUT",
// "DELETE", etc. It is useful for middleware and route handlers to differentiate
// between request types.
//
// Example Usage:
//
// q.Use(func(c *quick.Ctx) error {
// if c.Method() == "POST" {
// return c.Status(403).SendString("POST requests are not allowed")
// }
// return c.Next()
// })
//
// Returns:
// - string: The HTTP method (e.g., "GET", "POST", "PUT").
func (c *Ctx) Method() string {
return c.Request.Method
}
// Path retrieves the URL path of the incoming HTTP request.
//
// This method extracts the path component from the request URL, which
// is useful for routing and request handling.
//
// Example Usage:
//
// q.Get("/info", func(c *quick.Ctx) error {
// return c.Status(200).SendString("Requested Path: " + c.Path())
// })
//
// Returns:
// - string: The path component of the request URL (e.g., "/v1/user").
func (c *Ctx) Path() string {
return c.Request.URL.Path
}
// Host returns the host name from the HTTP request.
//
// This method extracts the host from `c.Request.Host`. If the request includes
// a port number (e.g., "localhost:3000"), it returns the full host including
// the port.
//
// Example Usage:
//
// q.Get("/", func(c *quick.Ctx) error {
// return c.Status(200).SendString("Host: " + c.Host())
// })
//
// Returns:
// - string: The host name from the request.
func (c *Ctx) Host() string {
if c.Request == nil {
return ""
}
return c.Request.Host
}
// QueryParam retrieves a query parameter value from the URL.
//
// Parameters:
// - key: The name of the query parameter to retrieve.
//
// Returns:
// - string: The value of the specified query parameter, or an empty string if not found.
func (c *Ctx) QueryParam(key string) string {
return c.Request.URL.Query().Get(key)
}
// QueryParam retrieves a query parameter value from the URL.
//
// Parameters:
// - key: The name of the query parameter to retrieve.
//
// Returns:
// - string: The value of the specified query parameter, or an empty string if not found.
func (c *Ctx) GetReqHeadersAll() map[string][]string {
return c.Headers
}
// GetHeadersAll returns all HTTP response headers stored in the context.
//
// Returns:
// - map[string][]string: A map containing all response headers with their values.
func (c *Ctx) GetHeadersAll() map[string][]string {
return c.Headers
}
// File serves a specific file to the client.
//
// This function trims any trailing "/*" from the provided file path, checks if it is a directory,
// and serves "index.html" if applicable. If the file exists, it is sent as the response.
//
// Parameters:
// - filePath: The path to the file to be served.
//
// Returns:
// - error: Always returns nil, as `http.ServeFile` handles errors internally.
func (c *Ctx) File(filePath string) error {
filePath = strings.TrimSuffix(filePath, "/*")
if stat, err := os.Stat(filePath); err == nil && stat.IsDir() {
filePath = filepath.Join(filePath, "index.html")
}
http.ServeFile(c.Response, c.Request, filePath)
return nil
}
// Bind parses and binds the request body to a Go struct.
//
// This function extracts and maps the request body content to the given struct (v).
// It supports various content types and ensures proper deserialization.
//
// Parameters:
// - v: A pointer to the structure where the request body will be bound.
//
// Returns:
// - error: An error if parsing fails or if the structure is incompatible with the request body.
func (c *Ctx) Bind(v interface{}) (err error) {
return extractParamsBind(c, v)
}
// BodyParser efficiently unmarshals the request body into the provided struct (v) based on the Content-Type header.
//
// Supported content-types:
// - application/json
// - application/xml, text/xml
//
// Parameters:
// - v: The target structure to decode the request body into.
//
// Returns:
// - error: An error if decoding fails or if the content-type is unsupported.
func (c *Ctx) BodyParser(v interface{}) error {
contentType := strings.ToLower(c.Request.Header.Get("Content-Type"))
switch {
case strings.HasPrefix(contentType, ContentTypeAppJSON):
return json.Unmarshal(c.bodyByte, v)
case strings.Contains(contentType, ContentTypeAppXML),
strings.Contains(contentType, ContentTypeTextXML):
return xml.Unmarshal(c.bodyByte, v)
default:
return fmt.Errorf("unsupported content-type: %s", contentType)
}
}
// Param retrieves the value of a URL parameter corresponding to the given key.
//
// This function searches for a parameter in the request's URL path and returns its value.
// If the parameter is not found, an empty string is returned.
//
// Parameters:
// - key: The name of the URL parameter to retrieve.
//
// Returns:
// - string: The value of the requested parameter or an empty string if not found.
func (c *Ctx) Param(key string) string {
val, ok := c.Params[key]
if ok {
return val
}
return ""
}
// Body retrieves the request body as a byte slice.
//
// This function returns the raw request body as a slice of bytes ([]byte).
//
// Returns:
// - []byte: The request body in its raw byte form.
func (c *Ctx) Body() []byte {
return c.bodyByte
}
// BodyString retrieves the request body as a string.
//
// This function converts the request body from a byte slice into a string format.
//
// Returns:
// - string: The request body as a string.
func (c *Ctx) BodyString() string {
return string(c.bodyByte)
}
// JSON encodes the provided interface (v) as JSON, sets the Content-Type header,
// and writes the response efficiently using buffer pooling.
//
// Parameters:
// - v: The data structure to encode as JSON.
//
// Returns:
// - error: An error if JSON encoding fails or if writing the response fails.
func (c *Ctx) JSON(v interface{}) error {
buf := acquireJSONBuffer()
defer releaseJSONBuffer(buf)
if err := json.NewEncoder(buf).Encode(v); err != nil {
return err
}
if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] == '\n' {
buf.Truncate(buf.Len() - 1)
}
c.writeResponse(buf.Bytes())
return nil
}
// JSONIN encodes the given interface as JSON with indentation and writes it to the HTTP response.
// Allows optional parameters to define the indentation format.
//
// ATTENTION
// use only for debugging, very slow
//
// Parameters:
// - v: The data structure to encode as JSON.
// - params (optional): Defines the indentation settings.
// - If params[0] is provided, it will be used as the prefix.
// - If params[1] is provided, it will be used as the indentation string.
//
// Returns:
// - error: An error if JSON encoding fails or if writing to the ResponseWriter fails.
func (c *Ctx) JSONIN(v interface{}, params ...string) error {
// Default indentation settings
prefix := ""
indent := " " // Default to 2 spaces
// Override if parameters are provided
if len(params) > 0 {
prefix = params[0]
}
if len(params) > 1 {
indent = params[1]
}
buf := acquireJSONBuffer()
defer releaseJSONBuffer(buf)
// Exemplo com JSON:
enc := json.NewEncoder(buf)
enc.SetIndent(prefix, indent)
if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] == '\n' {
buf.Truncate(buf.Len() - 1)
}
if err := enc.Encode(v); err != nil {
return err
}
c.writeResponse(buf.Bytes())
return nil
}
// XML serializes the given value to XML and writes it to the HTTP response.
// It avoids unnecessary memory allocations by using buffer pooling and ensures that no extra newline is appended.
//
// Parameters:
// - v: The data structure to encode as XML.
//
// Returns:
// - error: An error if XML encoding fails or if writing to the ResponseWriter fails.
func (c *Ctx) XML(v interface{}) error {
buf := acquireXMLBuffer()
defer releaseXMLBuffer(buf)
if err := xml.NewEncoder(buf).Encode(v); err != nil {
return err
}
if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] == '\n' {
buf.Truncate(buf.Len() - 1)
}
c.writeResponse(buf.Bytes())
return nil
}
// writeResponse writes the provided byte content to the ResponseWriter.
//
// If a custom status code (resStatus) has been set, it writes the header before the body.
//
// Parameters:
// - b: The byte slice to be written in the HTTP response.
//
// Returns:
// - error: An error if writing to the ResponseWriter fails.
func (c *Ctx) writeResponse(b []byte) error {
if c.Response == nil {
return errors.New("nil response writer")
}
if c.resStatus == 0 {
c.resStatus = http.StatusOK
}
c.Response.WriteHeader(c.resStatus)
_, err := c.Response.Write(b)
if flusher, ok := c.Response.(http.Flusher); ok {
flusher.Flush()
}
return err
}
// Byte writes a byte slice to the HTTP response.
//
// This function writes raw bytes to the response body using writeResponse().
//
// Parameters:
// - b: The byte slice to be written.
//
// Returns:
// - error: An error if the response write operation fails.
func (c *Ctx) Byte(b []byte) (err error) {
return c.writeResponse(b)
}
// Send writes a byte slice to the HTTP response.
//
// This function writes raw bytes to the response body using writeResponse().
//
// Parameters:
// - b: The byte slice to be written.
//
// Returns:
// - error: An error if the response write operation fails.
func (c *Ctx) Send(b []byte) (err error) {
return c.writeResponse(b)
}
// SendString writes a string to the HTTP response.
//
// This function converts the given string into a byte slice and writes it to the response body.
//
// Parameters:
// - s: The string to be written.
//
// Returns:
// - error: An error if the response write operation fails.
func (c *Ctx) SendString(s string) error {
return c.writeResponse([]byte(s))
}
// String writes a string to the HTTP response.
//
// This function converts the given string into a byte slice and writes it to the response body.
//
// Parameters:
// - s: The string to be written.
//
// Returns:
// - error: An error if the response write operation fails.
func (c *Ctx) String(s string) error {
return c.writeResponse([]byte(s))
}
// SendFile writes a file to the HTTP response as a byte slice.
//
// This function writes the provided byte slice (representing a file) to the response body.
//
// Parameters:
// - file: The file content as a byte slice.
//
// Returns:
// - error: An error if the response write operation fails.
func (c *Ctx) SendFile(file []byte) error {
_, err := c.Response.Write(file)
return err
}
func (c *Ctx) Del(key string) {
c.Response.Header().Del(key)
}
// Set defines an HTTP header in the response.
//
// This function sets the specified HTTP response header to the provided value.
//
// Parameters:
// - key: The name of the HTTP header to set.
// - value: The value to assign to the header.
func (c *Ctx) Set(key, value string) {
c.Response.Header().Set(key, value)
}
// Add defines an HTTP header in the response.
//
// This function sets the specified HTTP response header to the provided value.
//
// Parameters:
// - key: The name of the HTTP header to set.
// - value: The value to assign to the header.
func (c *Ctx) Add(key, value string) {
c.Response.Header().Add(key, value)
}
// Append adds a value to an HTTP response header.
//
// This function appends a new value to an existing HTTP response header.
//
// Parameters:
// - key: The name of the HTTP header.
// - value: The value to append to the header.
func (c *Ctx) Append(key, value string) {
c.Response.Header().Add(key, value)
}
// Accepts sets the "Accept" header in the HTTP response.
//
// This function assigns a specific accept type to the HTTP response header "Accept."
//
// Parameters:
// - acceptType: The MIME type to set in the "Accept" header.
//
// Returns:
// - *Ctx: The current context instance for method chaining.
func (c *Ctx) Accepts(acceptType string) *Ctx {
c.Response.Header().Set("Accept", acceptType)
return c
}
// Status sets the HTTP status code of the response.
//
// This function assigns a specific HTTP status code to the response.
//
// Parameters:
// - status: The HTTP status code to set.
//
// Returns:
// - *Ctx: The current context instance for method chaining.
func (c *Ctx) Status(status int) *Ctx {
c.resStatus = status
return c
}
//MultipartForm
// FormFileLimit sets the maximum allowed upload size.
//
// This function configures the maximum file upload size for multipart form-data requests.
//
// Parameters:
// - limit: A string representing the maximum file size (e.g., "10MB").
//
// Returns:
// - error: An error if the limit value is invalid.
func (c *Ctx) FormFileLimit(limit string) error {
size, err := parseSize(limit)
if err != nil {
return err
}
c.uploadFileSize = size
return nil
}
// FormFile processes an uploaded file and returns its details.
//
// This function retrieves the first uploaded file for the specified form field.
//
// Parameters:
// - fieldName: The name of the form field containing the uploaded file.
//
// Returns:
// - *UploadedFile: A struct containing the uploaded file details.
// - error: An error if no file is found or the retrieval fails.
func (c *Ctx) FormFile(fieldName string) (*UploadedFile, error) {
files, err := c.FormFiles(fieldName)
if err != nil {
return nil, err
}
if len(files) == 0 {
return nil, errors.New("no file uploaded")
}
return files[0], nil // Return the first file if multiple are uploaded
}
// fileWrapper wraps a bytes.Reader and adds a Close() method.
//
// This struct implements io.ReadCloser, allowing it to be used as a multipart.File.
// It ensures that the file can be read multiple times without losing data.
//
// Fields:
// - *bytes.Reader: A reader that holds the file content in memory.
type fileWrapper struct {
*bytes.Reader
}
// Close satisfies the io.ReadCloser interface.
//
// This function does nothing since the file is stored in memory
// and does not require explicit closing.
//
// Returns:
// - error: Always returns nil.
func (fw *fileWrapper) Close() error {
return nil
}
// FormFiles retrieves all uploaded files for the given field name.
//
// This function extracts all files uploaded in a multipart form request.
//
// Parameters:
// - fieldName: The name of the form field containing the uploaded files.
//
// Returns:
// - []*UploadedFile: A slice containing details of the uploaded files.
// - error: An error if no files are found or the retrieval fails.
func (c *Ctx) FormFiles(fieldName string) ([]*UploadedFile, error) {
if c.uploadFileSize == 0 {
c.uploadFileSize = 1 << 20 // set default 1MB
}
// check request
if c.Request == nil {
return nil, errors.New("HTTP request is nil")
}
// check body
if c.Request.Body == nil {
return nil, errors.New("request body is nil")
}
// check if `Content-Type` this ok
contentType := c.Request.Header.Get("Content-Type")
if !strings.HasPrefix(contentType, "multipart/form-data") {
return nil, errors.New("invalid content type, expected multipart/form-data")
}
// Parse multipart form with the defined limit
if err := c.Request.ParseMultipartForm(c.uploadFileSize); err != nil {
return nil, errors.New("failed to parse multipart form: " + err.Error())
}
// Debugging: Check if files exist
if c.Request.MultipartForm == nil || c.Request.MultipartForm.File[fieldName] == nil {
return nil, errors.New("no files found in the request")
}
// Retrieve all files for the given field name
files := c.Request.MultipartForm.File[fieldName]
if len(files) == 0 {
return nil, errors.New("no files found for field: " + fieldName)
}
var uploadedFiles []*UploadedFile
for _, handler := range files {
// Open file
file, err := handler.Open()
if err != nil {
return nil, errors.New("failed to open file: " + err.Error())
}
defer file.Close()
// Read file content into memory
var buf bytes.Buffer
if _, err := io.Copy(&buf, file); err != nil {
return nil, errors.New("failed to read file into buffer")
}
// reset multipart.File
// Create a reusable copy of the file
// that implements multipart.File correctly
fileCopy := &fileWrapper{bytes.NewReader(buf.Bytes())}
// Detect content type
contentType := http.DetectContentType(buf.Bytes())
// Append file details
uploadedFiles = append(uploadedFiles, &UploadedFile{
File: fileCopy,
Multipart: handler,
Info: FileInfo{
Filename: handler.Filename,
Size: handler.Size,
ContentType: contentType,
Bytes: buf.Bytes(),
},
})
}
return uploadedFiles, nil
}
// MultipartForm provides access to the raw multipart form data.
//
// This function parses and retrieves the multipart form from the request.
//
// Returns:
// - *multipart.Form: A pointer to the multipart form data.
// - error: An error if parsing fails.
func (c *Ctx) MultipartForm() (*multipart.Form, error) {
if err := c.Request.ParseMultipartForm(c.uploadFileSize); err != nil {
return nil, err
}
return c.Request.MultipartForm, nil
}
// FormValue retrieves a form value by its key.
//
// This function parses the form data and returns the value of the specified field.
//
// Parameters:
// - key: The name of the form field.
//
// Returns:
// - string: The value of the requested field, or an empty string if not found.
func (c *Ctx) FormValue(key string) string {
// Checks if the Content-Type is multipart
if c.Request.Header.Get("Content-Type") == "multipart/form-data" {
_ = c.Request.ParseMultipartForm(c.uploadFileSize) // Force correct processing
} else {
_ = c.Request.ParseForm() // For application/x-www-form-urlencoded
}
return c.Request.FormValue(key)
}
// FormValues retrieves all form values as a map.
//
// This function parses the form data and returns all form values.
//
// Returns:
// - map[string][]string: A map of form field names to their corresponding values.
func (c *Ctx) FormValues() map[string][]string {
// Checks if the Content-Type is multipart
if c.Request.Header.Get("Content-Type") == "multipart/form-data" {
_ = c.Request.ParseMultipartForm(c.uploadFileSize) // Required to process multipart
} else {
_ = c.Request.ParseForm() // Processes application/x-www-form-urlencoded
}
return c.Request.Form
}