github.com/influx6/npkg@v0.8.8/nhttp/context.go (about) 1 package nhttp 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "encoding/xml" 8 "errors" 9 "fmt" 10 htemplate "html/template" 11 "io" 12 "log" 13 "mime/multipart" 14 "net" 15 "net/http" 16 "net/url" 17 "os" 18 "path/filepath" 19 "strings" 20 "text/template" 21 22 "github.com/influx6/npkg/nerror" 23 "github.com/influx6/npkg/njson" 24 25 "github.com/influx6/npkg/nxid" 26 ) 27 28 const ( 29 defaultMemory = 64 << 20 // 64 MB 30 ) 31 32 // Render defines a giving type which exposes a Render method for 33 // rendering a custom output from a provided input string and bind 34 // object. 35 type Render interface { 36 Render(io.Writer, string, interface{}) error 37 } 38 39 // Options defines a function type which receives a Ctx pointer and 40 // sets/modifiers it's internal state values. 41 type Options func(*Ctx) 42 43 // Apply applies giving options against Ctx instance returning context again. 44 func Apply(c *Ctx, ops ...Options) *Ctx { 45 for _, op := range ops { 46 op(c) 47 } 48 49 return c 50 } 51 52 // SetID sets the id of the giving context. 53 func SetID(id nxid.ID) Options { 54 return func(c *Ctx) { 55 c.id = id 56 } 57 } 58 59 // SetMultipartFormSize sets the expected size for any multipart data. 60 func SetMultipartFormSize(size int64) Options { 61 return func(c *Ctx) { 62 c.multipartFormSize = size 63 } 64 } 65 66 // SetPath sets the path of the giving context. 67 func SetPath(p string) Options { 68 return func(c *Ctx) { 69 c.path = p 70 } 71 } 72 73 // SetRenderer will returns a function to set the render used by a giving context. 74 func SetRenderer(r Render) Options { 75 return func(c *Ctx) { 76 c.render = r 77 } 78 } 79 80 // SetResponseWriter returns a option function to set the response of a Ctx. 81 func SetResponseWriter(w http.ResponseWriter, beforeFuncs ...func()) Options { 82 return func(c *Ctx) { 83 c.response = &Response{ 84 beforeFuncs: beforeFuncs, 85 Writer: w, 86 } 87 } 88 } 89 90 // SetResponse returns a option function to set the response of a Ctx. 91 func SetResponse(r *Response) Options { 92 return func(c *Ctx) { 93 c.response = r 94 } 95 } 96 97 // SetRequest returns a option function to set the request of a Ctx. 98 func SetRequest(r *http.Request) Options { 99 return func(c *Ctx) { 100 c.request = r 101 c.ctx = r.Context() 102 if err := c.InitForms(); err != nil { 103 var wrapErr = nerror.WrapOnly(err) 104 log.Printf("Failed to initialize forms: %+q", wrapErr) 105 } 106 } 107 } 108 109 // SetNotFound will return a function to set the NotFound handler for a giving context. 110 func SetNotFound(r ContextHandler) Options { 111 return func(c *Ctx) { 112 c.notfoundHandler = r 113 } 114 } 115 116 //========================================================================================= 117 118 // Ctx defines a http related context object for a request session 119 // which is to be served. 120 type Ctx struct { 121 ctx context.Context 122 123 multipartFormSize int64 124 id nxid.ID 125 path string 126 render Render 127 response *Response 128 query url.Values 129 request *http.Request 130 flash map[string][]string 131 params map[string]string 132 notfoundHandler ContextHandler 133 } 134 135 // NewContext returns a new Ctx with the Options slice applied. 136 func NewContext(ops ...Options) *Ctx { 137 c := &Ctx{ 138 id: nxid.New(), 139 flash: map[string][]string{}, 140 params: map[string]string{}, 141 } 142 143 if c.multipartFormSize <= 0 { 144 c.multipartFormSize = defaultMemory 145 } 146 147 for _, op := range ops { 148 if op == nil { 149 continue 150 } 151 152 op(c) 153 } 154 155 return c 156 } 157 158 // ID returns the unique id of giving request context. 159 func (c *Ctx) ID() nxid.ID { 160 return c.id 161 } 162 163 // Context returns the underline context.ctx for the request. 164 func (c *Ctx) Context() context.Context { 165 return c.ctx 166 } 167 168 // Header returns the header associated with the giving request. 169 func (c *Ctx) Header() http.Header { 170 return c.request.Header 171 } 172 173 // GetHeader returns associated value of key from request headers. 174 func (c *Ctx) GetHeader(key string) string { 175 if c.request == nil { 176 return "" 177 } 178 179 return c.request.Header.Get(key) 180 } 181 182 // AddHeader adds te value into the giving key into the response object header. 183 func (c *Ctx) AddHeader(key string, value string) { 184 if c.response == nil { 185 return 186 } 187 188 c.response.Header().Add(key, value) 189 } 190 191 // AddParam adds a new parameter value into the Ctx. 192 // 193 // This is not safe for concurrent use. 194 func (c *Ctx) AddParam(key string, value string) { 195 c.params[key] = value 196 } 197 198 // AddForm adds a new form value into the Ctx. 199 // 200 // This is not safe for concurrent use. 201 func (c *Ctx) Param(key string) string { 202 return c.params[key] 203 } 204 205 // SetHeader sets te key-value pair into the response object header. 206 func (c *Ctx) SetHeader(key string, value string) { 207 if c.response == nil { 208 return 209 } 210 211 c.response.Header().Set(key, value) 212 } 213 214 // HasHeader returns true/false if string.Contains validate giving header key 215 // has value within string of the request header. 216 // if value is an empty string, then method only validates that you 217 // have key in headers. 218 func (c *Ctx) HasHeader(key string, value string) bool { 219 if c.request == nil { 220 return false 221 } 222 223 if value == "" { 224 return c.request.Header.Get(key) != "" 225 } 226 227 return strings.Contains(c.request.Header.Get(key), value) 228 } 229 230 // Request returns the associated request. 231 func (c *Ctx) Request() *http.Request { 232 return c.request 233 } 234 235 // Body returns the associated io.ReadCloser which is the body of the Request. 236 func (c *Ctx) Body() io.ReadCloser { 237 return c.request.Body 238 } 239 240 // Response returns the associated response object for this context. 241 func (c *Ctx) Response() *Response { 242 return c.response 243 } 244 245 // IsTLS returns true/false if the giving reqest is a tls connection. 246 func (c *Ctx) IsTLS() bool { 247 return c.request.TLS != nil 248 } 249 250 // IsWebSocket returns true/false if the giving reqest is a websocket connection. 251 func (c *Ctx) IsWebSocket() bool { 252 upgrade := c.request.Header.Get(HeaderUpgrade) 253 return upgrade == "websocket" || upgrade == "Websocket" 254 } 255 256 // Scheme attempts to return the exact url scheme of the request. 257 func (c *Ctx) Scheme() string { 258 // Can't use `r.Request.URL.Scheme` 259 // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0 260 if c.IsTLS() { 261 return "https" 262 } 263 if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" { 264 return scheme 265 } 266 if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" { 267 return scheme 268 } 269 if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" { 270 return "https" 271 } 272 if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" { 273 return scheme 274 } 275 return "http" 276 } 277 278 // RealIP attempts to return the ip of the giving request. 279 func (c *Ctx) RealIP() string { 280 ra := c.request.RemoteAddr 281 if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { 282 ra = strings.Split(ip, ", ")[0] 283 } else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { 284 ra = ip 285 } else { 286 ra, _, _ = net.SplitHostPort(ra) 287 } 288 return ra 289 } 290 291 // Path returns the request path associated with the context. 292 func (c *Ctx) Path() string { 293 if c.path == "" && c.request != nil { 294 c.path = c.request.URL.Path 295 } 296 297 return c.path 298 } 299 300 // QueryParam finds the giving value for the giving name in the querie set. 301 func (c *Ctx) QueryParam(name string) string { 302 if c.query == nil { 303 c.query = c.request.URL.Query() 304 } 305 306 return c.query.Get(name) 307 } 308 309 // QueryParams returns the context url.Values object. 310 func (c *Ctx) QueryParams() url.Values { 311 if c.query == nil { 312 c.query = c.request.URL.Query() 313 } 314 return c.query 315 } 316 317 // QueryString returns the raw query portion of the request path. 318 func (c *Ctx) QueryString() string { 319 return c.request.URL.RawQuery 320 } 321 322 // Form returns the url.Values of the giving request. 323 func (c *Ctx) Form() url.Values { 324 return c.request.Form 325 } 326 327 // FormValue returns the value of the giving item from the form fields. 328 func (c *Ctx) FormValue(name string) string { 329 return c.request.FormValue(name) 330 } 331 332 // FormParams returns a url.Values which contains the parse form values for 333 // multipart or wwww-urlencoded forms. 334 func (c *Ctx) FormParams() (url.Values, error) { 335 if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) { 336 if err := c.request.ParseMultipartForm(c.multipartFormSize); err != nil { 337 return nil, err 338 } 339 } else { 340 if err := c.request.ParseForm(); err != nil { 341 return nil, err 342 } 343 } 344 return c.request.Form, nil 345 } 346 347 // FormFile returns the giving FileHeader for the giving name. 348 func (c *Ctx) FormFile(name string) (*multipart.FileHeader, error) { 349 _, fh, err := c.request.FormFile(name) 350 return fh, err 351 } 352 353 // MultipartForm returns the multipart form of the giving request if its a multipart 354 // request. 355 func (c *Ctx) MultipartForm() (*multipart.Form, error) { 356 err := c.request.ParseMultipartForm(defaultMemory) 357 return c.request.MultipartForm, err 358 } 359 360 // Cookie returns the associated cookie by the giving name. 361 func (c *Ctx) Cookie(name string) (*http.Cookie, error) { 362 return c.request.Cookie(name) 363 } 364 365 // SetCookie sets the cookie into the response object. 366 func (c *Ctx) SetCookie(cookie *http.Cookie) { 367 http.SetCookie(c.response, cookie) 368 } 369 370 // Cookies returns the associated cookies slice of the http request. 371 func (c *Ctx) Cookies() []*http.Cookie { 372 return c.request.Cookies() 373 } 374 375 // ErrNoRenderInitiated defines the error returned when a renderer is not set 376 // but Ctx.Render() is called. 377 var ErrNoRenderInitiated = errors.New("Renderer was not set or is uninitiated") 378 379 // Render renders the giving string with data binding using the provided Render 380 // of the context. 381 func (c *Ctx) Render(code int, tmpl string, data interface{}) (err error) { 382 if c.render == nil { 383 return ErrNoRenderInitiated 384 } 385 386 buf := new(bytes.Buffer) 387 388 if err = c.render.Render(buf, tmpl, data); err != nil { 389 return 390 } 391 392 return c.HTMLBlob(code, buf.Bytes()) 393 } 394 395 // Template renders provided template.Template object into the response object. 396 func (c *Ctx) Template(code int, tmpl *template.Template, data interface{}) error { 397 c.Status(code) 398 return tmpl.Funcs(TextContextFunctions(c)).Execute(c.response, data) 399 } 400 401 // HTMLTemplate renders provided template.Template object into the response object. 402 func (c *Ctx) HTMLTemplate(code int, tmpl *htemplate.Template, data interface{}) error { 403 c.Status(code) 404 return tmpl.Funcs(HTMLContextFunctions(c)).Execute(c.response, data) 405 } 406 407 // HTML renders giving html into response. 408 func (c *Ctx) HTML(code int, html string) (err error) { 409 return c.HTMLBlob(code, []byte(html)) 410 } 411 412 // HTMLBlob renders giving html into response. 413 func (c *Ctx) HTMLBlob(code int, b []byte) (err error) { 414 return c.Blob(code, MIMETextHTMLCharsetUTF8, b) 415 } 416 417 // Error renders giving error response into response. 418 func (c *Ctx) Error(statusCode int, errorCode string, message string, err error) error { 419 c.response.Header().Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8) 420 return JSONError(c.Response(), statusCode, errorCode, message, err) 421 } 422 423 // String renders giving string into response. 424 func (c *Ctx) String(code int, s string) (err error) { 425 return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s)) 426 } 427 428 // NJSON renders njson object as json response. 429 func (c *Ctx) NJSON(code int, data *njson.JSON) (err error) { 430 c.response.Header().Set(HeaderContentType, MIMEApplicationJSON) 431 c.response.WriteHeader(code) 432 _, err = data.WriteTo(c.response) 433 return 434 } 435 436 // JSON renders giving json data into response. 437 func (c *Ctx) JSON(code int, i interface{}) (err error) { 438 _, pretty := c.QueryParams()["pretty"] 439 if pretty { 440 return c.JSONPretty(code, i, " ") 441 } 442 b, err := json.Marshal(i) 443 if err != nil { 444 return 445 } 446 return c.JSONBlob(code, b) 447 } 448 449 // JSONPretty renders giving json data as indented into response. 450 func (c *Ctx) JSONPretty(code int, i interface{}, indent string) (err error) { 451 b, err := json.MarshalIndent(i, "", indent) 452 if err != nil { 453 return 454 } 455 return c.JSONBlob(code, b) 456 } 457 458 // JSONBlob renders giving json data into response with proper mime type. 459 func (c *Ctx) JSONBlob(code int, b []byte) (err error) { 460 return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b) 461 } 462 463 // JSONP renders giving jsonp as response with proper mime type. 464 func (c *Ctx) JSONP(code int, callback string, i interface{}) (err error) { 465 b, err := json.Marshal(i) 466 if err != nil { 467 return 468 } 469 return c.JSONPBlob(code, callback, b) 470 } 471 472 // JSONPBlob renders giving jsonp as response with proper mime type. 473 func (c *Ctx) JSONPBlob(code int, callback string, b []byte) (err error) { 474 c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8) 475 c.response.WriteHeader(code) 476 if _, err = c.response.Write([]byte(callback + "(")); err != nil { 477 return 478 } 479 if _, err = c.response.Write(b); err != nil { 480 return 481 } 482 _, err = c.response.Write([]byte(");")) 483 return 484 } 485 486 // XML renders giving xml as response with proper mime type. 487 func (c *Ctx) XML(code int, i interface{}) (err error) { 488 _, pretty := c.QueryParams()["pretty"] 489 if pretty { 490 return c.XMLPretty(code, i, " ") 491 } 492 b, err := xml.Marshal(i) 493 if err != nil { 494 return 495 } 496 return c.XMLBlob(code, b) 497 } 498 499 // XMLPretty renders giving xml as indent as response with proper mime type. 500 func (c *Ctx) XMLPretty(code int, i interface{}, indent string) (err error) { 501 b, err := xml.MarshalIndent(i, "", indent) 502 if err != nil { 503 return 504 } 505 return c.XMLBlob(code, b) 506 } 507 508 // XMLBlob renders giving xml as response with proper mime type. 509 func (c *Ctx) XMLBlob(code int, b []byte) (err error) { 510 c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8) 511 c.response.WriteHeader(code) 512 if _, err = c.response.Write([]byte(xml.Header)); err != nil { 513 return 514 } 515 _, err = c.response.Write(b) 516 return 517 } 518 519 // Blob write giving byte slice as response with proper mime type. 520 func (c *Ctx) Blob(code int, contentType string, b []byte) (err error) { 521 c.response.Header().Set(HeaderContentType, contentType) 522 c.response.WriteHeader(code) 523 _, err = c.response.Write(b) 524 return 525 } 526 527 // Stream copies giving io.Readers content into response. 528 func (c *Ctx) Stream(code int, contentType string, r io.Reader) (err error) { 529 c.response.Header().Set(HeaderContentType, contentType) 530 c.response.WriteHeader(code) 531 _, err = io.Copy(c.response, r) 532 return 533 } 534 535 // File streams file content into response. 536 func (c *Ctx) File(file string) (err error) { 537 f, err := os.Open(file) 538 if err != nil { 539 return err 540 } 541 542 defer f.Close() 543 544 fi, _ := f.Stat() 545 if fi.IsDir() { 546 file = filepath.Join(file, "index.html") 547 f, err = os.Open(file) 548 if err != nil { 549 return 550 } 551 552 defer f.Close() 553 if fi, err = f.Stat(); err != nil { 554 return 555 } 556 } 557 558 http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f) 559 return 560 } 561 562 // Attachment attempts to attach giving file details. 563 func (c *Ctx) Attachment(file, name string) (err error) { 564 return c.contentDisposition(file, name, "attachment") 565 } 566 567 // Inline attempts to inline file content. 568 func (c *Ctx) Inline(file, name string) (err error) { 569 return c.contentDisposition(file, name, "inline") 570 } 571 572 func (c *Ctx) contentDisposition(file, name, dispositionType string) (err error) { 573 c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s", dispositionType, name)) 574 c.File(file) 575 return 576 } 577 578 // SetFlash sets giving message/messages into the slice bucket of the 579 // given name list. 580 func (c *Ctx) SetFlash(name string, message string) { 581 c.flash[name] = append(c.flash[name], message) 582 } 583 584 // ClearFlashMessages clears all available message items within 585 // the flash map. 586 func (c *Ctx) ClearFlashMessages() { 587 c.flash = make(map[string][]string) 588 } 589 590 // FlashMessages returns available map of all flash messages. 591 // A copy is sent not the context currently used instance. 592 func (c *Ctx) FlashMessages() map[string][]string { 593 copy := make(map[string][]string) 594 for name, messages := range c.flash { 595 copy[name] = append([]string{}, messages...) 596 } 597 return copy 598 } 599 600 // ClearFlash removes all available message items from the context flash message 601 // map. 602 func (c *Ctx) ClearFlash(name string) { 603 if _, ok := c.flash[name]; ok { 604 c.flash[name] = nil 605 } 606 } 607 608 // Flash returns an associated slice of messages/string, for giving 609 // flash name/key. 610 func (c *Ctx) Flash(name string) []string { 611 messages := c.flash[name] 612 return messages 613 } 614 615 // ModContext executes provided function against current context 616 // modifying the current context with the returned and updating 617 // underlying request with new context Ctx. 618 // 619 // It is not safe for concurrent use. 620 func (c *Ctx) ModContext(modder func(ctx context.Context) context.Context) { 621 if c.ctx == nil { 622 return 623 } 624 625 var newCtx = modder(c.ctx) 626 c.request = c.request.WithContext(newCtx) 627 c.ctx = c.request.Context() 628 } 629 630 // NotFound writes calls the giving response against the NotFound handler 631 // if present, else uses a http.StatusMovedPermanently status code. 632 func (c *Ctx) NotFound() error { 633 if c.notfoundHandler != nil { 634 return c.notfoundHandler(c) 635 } 636 637 c.response.WriteHeader(http.StatusMovedPermanently) 638 return nil 639 } 640 641 // Status writes status code without writing content to response. 642 func (c *Ctx) Status(code int) { 643 c.response.WriteHeader(code) 644 } 645 646 // NoContent writes status code without writing content to response. 647 func (c *Ctx) NoContent(code int) error { 648 c.response.WriteHeader(code) 649 return nil 650 } 651 652 // ErrInvalidRedirectCode is error returned when redirect code is wrong. 653 var ErrInvalidRedirectCode = errors.New("Invalid redirect code") 654 655 // Redirect redirects context response. 656 func (c *Ctx) Redirect(code int, url string) error { 657 if code < 300 || code > 308 { 658 return ErrInvalidRedirectCode 659 } 660 661 c.response.Header().Set(HeaderLocation, url) 662 c.response.WriteHeader(code) 663 return nil 664 } 665 666 // InitForms will call the appropriate function to parse the necessary form values 667 // within the giving request context. 668 func (c *Ctx) InitForms() error { 669 if c.request == nil { 670 return nil 671 } 672 673 if _, err := c.FormParams(); err != nil { 674 return err 675 } 676 return nil 677 } 678 679 // Reset resets context internal fields 680 func (c *Ctx) Reset(r *http.Request, w http.ResponseWriter) error { 681 if r == nil && w == nil { 682 c.request = nil 683 c.response = nil 684 c.query = nil 685 c.notfoundHandler = nil 686 c.params = nil 687 c.flash = nil 688 c.ctx = nil 689 return nil 690 } 691 692 c.request = r 693 c.query = nil 694 c.id = nxid.New() 695 c.ctx = r.Context() 696 c.notfoundHandler = nil 697 c.params = map[string]string{} 698 c.flash = map[string][]string{} 699 700 if c.multipartFormSize <= 0 { 701 c.multipartFormSize = defaultMemory 702 } 703 704 c.request = r 705 c.response = &Response{Writer: w} 706 return c.InitForms() 707 }