1package echo 2 3import ( 4 "bytes" 5 "encoding/json" 6 "encoding/xml" 7 "fmt" 8 "io" 9 "mime/multipart" 10 "net" 11 "net/http" 12 "net/url" 13 "os" 14 "path/filepath" 15 "strings" 16) 17 18type ( 19 // Context represents the context of the current HTTP request. It holds request and 20 // response objects, path, path parameters, data and registered handler. 21 Context interface { 22 // Request returns `*http.Request`. 23 Request() *http.Request 24 25 // SetRequest sets `*http.Request`. 26 SetRequest(r *http.Request) 27 28 // Response returns `*Response`. 29 Response() *Response 30 31 // IsTLS returns true if HTTP connection is TLS otherwise false. 32 IsTLS() bool 33 34 // IsWebSocket returns true if HTTP connection is WebSocket otherwise false. 35 IsWebSocket() bool 36 37 // Scheme returns the HTTP protocol scheme, `http` or `https`. 38 Scheme() string 39 40 // RealIP returns the client's network address based on `X-Forwarded-For` 41 // or `X-Real-IP` request header. 42 RealIP() string 43 44 // Path returns the registered path for the handler. 45 Path() string 46 47 // SetPath sets the registered path for the handler. 48 SetPath(p string) 49 50 // Param returns path parameter by name. 51 Param(name string) string 52 53 // ParamNames returns path parameter names. 54 ParamNames() []string 55 56 // SetParamNames sets path parameter names. 57 SetParamNames(names ...string) 58 59 // ParamValues returns path parameter values. 60 ParamValues() []string 61 62 // SetParamValues sets path parameter values. 63 SetParamValues(values ...string) 64 65 // QueryParam returns the query param for the provided name. 66 QueryParam(name string) string 67 68 // QueryParams returns the query parameters as `url.Values`. 69 QueryParams() url.Values 70 71 // QueryString returns the URL query string. 72 QueryString() string 73 74 // FormValue returns the form field value for the provided name. 75 FormValue(name string) string 76 77 // FormParams returns the form parameters as `url.Values`. 78 FormParams() (url.Values, error) 79 80 // FormFile returns the multipart form file for the provided name. 81 FormFile(name string) (*multipart.FileHeader, error) 82 83 // MultipartForm returns the multipart form. 84 MultipartForm() (*multipart.Form, error) 85 86 // Cookie returns the named cookie provided in the request. 87 Cookie(name string) (*http.Cookie, error) 88 89 // SetCookie adds a `Set-Cookie` header in HTTP response. 90 SetCookie(cookie *http.Cookie) 91 92 // Cookies returns the HTTP cookies sent with the request. 93 Cookies() []*http.Cookie 94 95 // Get retrieves data from the context. 96 Get(key string) interface{} 97 98 // Set saves data in the context. 99 Set(key string, val interface{}) 100 101 // Bind binds the request body into provided type `i`. The default binder 102 // does it based on Content-Type header. 103 Bind(i interface{}) error 104 105 // Validate validates provided `i`. It is usually called after `Context#Bind()`. 106 // Validator must be registered using `Echo#Validator`. 107 Validate(i interface{}) error 108 109 // Render renders a template with data and sends a text/html response with status 110 // code. Renderer must be registered using `Echo.Renderer`. 111 Render(code int, name string, data interface{}) error 112 113 // HTML sends an HTTP response with status code. 114 HTML(code int, html string) error 115 116 // HTMLBlob sends an HTTP blob response with status code. 117 HTMLBlob(code int, b []byte) error 118 119 // String sends a string response with status code. 120 String(code int, s string) error 121 122 // JSON sends a JSON response with status code. 123 JSON(code int, i interface{}) error 124 125 // JSONPretty sends a pretty-print JSON with status code. 126 JSONPretty(code int, i interface{}, indent string) error 127 128 // JSONBlob sends a JSON blob response with status code. 129 JSONBlob(code int, b []byte) error 130 131 // JSONP sends a JSONP response with status code. It uses `callback` to construct 132 // the JSONP payload. 133 JSONP(code int, callback string, i interface{}) error 134 135 // JSONPBlob sends a JSONP blob response with status code. It uses `callback` 136 // to construct the JSONP payload. 137 JSONPBlob(code int, callback string, b []byte) error 138 139 // XML sends an XML response with status code. 140 XML(code int, i interface{}) error 141 142 // XMLPretty sends a pretty-print XML with status code. 143 XMLPretty(code int, i interface{}, indent string) error 144 145 // XMLBlob sends an XML blob response with status code. 146 XMLBlob(code int, b []byte) error 147 148 // Blob sends a blob response with status code and content type. 149 Blob(code int, contentType string, b []byte) error 150 151 // Stream sends a streaming response with status code and content type. 152 Stream(code int, contentType string, r io.Reader) error 153 154 // File sends a response with the content of the file. 155 File(file string) error 156 157 // Attachment sends a response as attachment, prompting client to save the 158 // file. 159 Attachment(file string, name string) error 160 161 // Inline sends a response as inline, opening the file in the browser. 162 Inline(file string, name string) error 163 164 // NoContent sends a response with no body and a status code. 165 NoContent(code int) error 166 167 // Redirect redirects the request to a provided URL with status code. 168 Redirect(code int, url string) error 169 170 // Error invokes the registered HTTP error handler. Generally used by middleware. 171 Error(err error) 172 173 // Handler returns the matched handler by router. 174 Handler() HandlerFunc 175 176 // SetHandler sets the matched handler by router. 177 SetHandler(h HandlerFunc) 178 179 // Logger returns the `Logger` instance. 180 Logger() Logger 181 182 // Echo returns the `Echo` instance. 183 Echo() *Echo 184 185 // Reset resets the context after request completes. It must be called along 186 // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`. 187 // See `Echo#ServeHTTP()` 188 Reset(r *http.Request, w http.ResponseWriter) 189 } 190 191 context struct { 192 request *http.Request 193 response *Response 194 path string 195 pnames []string 196 pvalues []string 197 query url.Values 198 handler HandlerFunc 199 store Map 200 echo *Echo 201 } 202) 203 204const ( 205 defaultMemory = 32 << 20 // 32 MB 206 indexPage = "index.html" 207 defaultIndent = " " 208) 209 210func (c *context) writeContentType(value string) { 211 header := c.Response().Header() 212 if header.Get(HeaderContentType) == "" { 213 header.Set(HeaderContentType, value) 214 } 215} 216 217func (c *context) Request() *http.Request { 218 return c.request 219} 220 221func (c *context) SetRequest(r *http.Request) { 222 c.request = r 223} 224 225func (c *context) Response() *Response { 226 return c.response 227} 228 229func (c *context) IsTLS() bool { 230 return c.request.TLS != nil 231} 232 233func (c *context) IsWebSocket() bool { 234 upgrade := c.request.Header.Get(HeaderUpgrade) 235 return upgrade == "websocket" || upgrade == "Websocket" 236} 237 238func (c *context) Scheme() string { 239 // Can't use `r.Request.URL.Scheme` 240 // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0 241 if c.IsTLS() { 242 return "https" 243 } 244 if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" { 245 return scheme 246 } 247 if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" { 248 return scheme 249 } 250 if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" { 251 return "https" 252 } 253 if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" { 254 return scheme 255 } 256 return "http" 257} 258 259func (c *context) RealIP() string { 260 if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { 261 return strings.Split(ip, ", ")[0] 262 } 263 if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { 264 return ip 265 } 266 ra, _, _ := net.SplitHostPort(c.request.RemoteAddr) 267 return ra 268} 269 270func (c *context) Path() string { 271 return c.path 272} 273 274func (c *context) SetPath(p string) { 275 c.path = p 276} 277 278func (c *context) Param(name string) string { 279 for i, n := range c.pnames { 280 if i < len(c.pvalues) { 281 if n == name { 282 return c.pvalues[i] 283 } 284 } 285 } 286 return "" 287} 288 289func (c *context) ParamNames() []string { 290 return c.pnames 291} 292 293func (c *context) SetParamNames(names ...string) { 294 c.pnames = names 295} 296 297func (c *context) ParamValues() []string { 298 return c.pvalues[:len(c.pnames)] 299} 300 301func (c *context) SetParamValues(values ...string) { 302 c.pvalues = values 303} 304 305func (c *context) QueryParam(name string) string { 306 if c.query == nil { 307 c.query = c.request.URL.Query() 308 } 309 return c.query.Get(name) 310} 311 312func (c *context) QueryParams() url.Values { 313 if c.query == nil { 314 c.query = c.request.URL.Query() 315 } 316 return c.query 317} 318 319func (c *context) QueryString() string { 320 return c.request.URL.RawQuery 321} 322 323func (c *context) FormValue(name string) string { 324 return c.request.FormValue(name) 325} 326 327func (c *context) FormParams() (url.Values, error) { 328 if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) { 329 if err := c.request.ParseMultipartForm(defaultMemory); err != nil { 330 return nil, err 331 } 332 } else { 333 if err := c.request.ParseForm(); err != nil { 334 return nil, err 335 } 336 } 337 return c.request.Form, nil 338} 339 340func (c *context) FormFile(name string) (*multipart.FileHeader, error) { 341 _, fh, err := c.request.FormFile(name) 342 return fh, err 343} 344 345func (c *context) MultipartForm() (*multipart.Form, error) { 346 err := c.request.ParseMultipartForm(defaultMemory) 347 return c.request.MultipartForm, err 348} 349 350func (c *context) Cookie(name string) (*http.Cookie, error) { 351 return c.request.Cookie(name) 352} 353 354func (c *context) SetCookie(cookie *http.Cookie) { 355 http.SetCookie(c.Response(), cookie) 356} 357 358func (c *context) Cookies() []*http.Cookie { 359 return c.request.Cookies() 360} 361 362func (c *context) Get(key string) interface{} { 363 return c.store[key] 364} 365 366func (c *context) Set(key string, val interface{}) { 367 if c.store == nil { 368 c.store = make(Map) 369 } 370 c.store[key] = val 371} 372 373func (c *context) Bind(i interface{}) error { 374 return c.echo.Binder.Bind(i, c) 375} 376 377func (c *context) Validate(i interface{}) error { 378 if c.echo.Validator == nil { 379 return ErrValidatorNotRegistered 380 } 381 return c.echo.Validator.Validate(i) 382} 383 384func (c *context) Render(code int, name string, data interface{}) (err error) { 385 if c.echo.Renderer == nil { 386 return ErrRendererNotRegistered 387 } 388 buf := new(bytes.Buffer) 389 if err = c.echo.Renderer.Render(buf, name, data, c); err != nil { 390 return 391 } 392 return c.HTMLBlob(code, buf.Bytes()) 393} 394 395func (c *context) HTML(code int, html string) (err error) { 396 return c.HTMLBlob(code, []byte(html)) 397} 398 399func (c *context) HTMLBlob(code int, b []byte) (err error) { 400 return c.Blob(code, MIMETextHTMLCharsetUTF8, b) 401} 402 403func (c *context) String(code int, s string) (err error) { 404 return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s)) 405} 406 407func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) { 408 enc := json.NewEncoder(c.response) 409 _, pretty := c.QueryParams()["pretty"] 410 if c.echo.Debug || pretty { 411 enc.SetIndent("", " ") 412 } 413 c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) 414 c.response.WriteHeader(code) 415 if _, err = c.response.Write([]byte(callback + "(")); err != nil { 416 return 417 } 418 if err = enc.Encode(i); err != nil { 419 return 420 } 421 if _, err = c.response.Write([]byte(");")); err != nil { 422 return 423 } 424 return 425} 426 427func (c *context) json(code int, i interface{}, indent string) error { 428 enc := json.NewEncoder(c.response) 429 if indent != "" { 430 enc.SetIndent("", indent) 431 } 432 c.writeContentType(MIMEApplicationJSONCharsetUTF8) 433 c.response.WriteHeader(code) 434 return enc.Encode(i) 435} 436 437func (c *context) JSON(code int, i interface{}) (err error) { 438 indent := "" 439 if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty { 440 indent = defaultIndent 441 } 442 return c.json(code, i, indent) 443} 444 445func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) { 446 return c.json(code, i, indent) 447} 448 449func (c *context) JSONBlob(code int, b []byte) (err error) { 450 return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b) 451} 452 453func (c *context) JSONP(code int, callback string, i interface{}) (err error) { 454 return c.jsonPBlob(code, callback, i) 455} 456 457func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) { 458 c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) 459 c.response.WriteHeader(code) 460 if _, err = c.response.Write([]byte(callback + "(")); err != nil { 461 return 462 } 463 if _, err = c.response.Write(b); err != nil { 464 return 465 } 466 _, err = c.response.Write([]byte(");")) 467 return 468} 469 470func (c *context) xml(code int, i interface{}, indent string) (err error) { 471 c.writeContentType(MIMEApplicationXMLCharsetUTF8) 472 c.response.WriteHeader(code) 473 enc := xml.NewEncoder(c.response) 474 if indent != "" { 475 enc.Indent("", indent) 476 } 477 if _, err = c.response.Write([]byte(xml.Header)); err != nil { 478 return 479 } 480 return enc.Encode(i) 481} 482 483func (c *context) XML(code int, i interface{}) (err error) { 484 indent := "" 485 if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty { 486 indent = defaultIndent 487 } 488 return c.xml(code, i, indent) 489} 490 491func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) { 492 return c.xml(code, i, indent) 493} 494 495func (c *context) XMLBlob(code int, b []byte) (err error) { 496 c.writeContentType(MIMEApplicationXMLCharsetUTF8) 497 c.response.WriteHeader(code) 498 if _, err = c.response.Write([]byte(xml.Header)); err != nil { 499 return 500 } 501 _, err = c.response.Write(b) 502 return 503} 504 505func (c *context) Blob(code int, contentType string, b []byte) (err error) { 506 c.writeContentType(contentType) 507 c.response.WriteHeader(code) 508 _, err = c.response.Write(b) 509 return 510} 511 512func (c *context) Stream(code int, contentType string, r io.Reader) (err error) { 513 c.writeContentType(contentType) 514 c.response.WriteHeader(code) 515 _, err = io.Copy(c.response, r) 516 return 517} 518 519func (c *context) File(file string) (err error) { 520 f, err := os.Open(file) 521 if err != nil { 522 return NotFoundHandler(c) 523 } 524 defer f.Close() 525 526 fi, _ := f.Stat() 527 if fi.IsDir() { 528 file = filepath.Join(file, indexPage) 529 f, err = os.Open(file) 530 if err != nil { 531 return NotFoundHandler(c) 532 } 533 defer f.Close() 534 if fi, err = f.Stat(); err != nil { 535 return 536 } 537 } 538 http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f) 539 return 540} 541 542func (c *context) Attachment(file, name string) error { 543 return c.contentDisposition(file, name, "attachment") 544} 545 546func (c *context) Inline(file, name string) error { 547 return c.contentDisposition(file, name, "inline") 548} 549 550func (c *context) contentDisposition(file, name, dispositionType string) error { 551 c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name)) 552 return c.File(file) 553} 554 555func (c *context) NoContent(code int) error { 556 c.response.WriteHeader(code) 557 return nil 558} 559 560func (c *context) Redirect(code int, url string) error { 561 if code < 300 || code > 308 { 562 return ErrInvalidRedirectCode 563 } 564 c.response.Header().Set(HeaderLocation, url) 565 c.response.WriteHeader(code) 566 return nil 567} 568 569func (c *context) Error(err error) { 570 c.echo.HTTPErrorHandler(err, c) 571} 572 573func (c *context) Echo() *Echo { 574 return c.echo 575} 576 577func (c *context) Handler() HandlerFunc { 578 return c.handler 579} 580 581func (c *context) SetHandler(h HandlerFunc) { 582 c.handler = h 583} 584 585func (c *context) Logger() Logger { 586 return c.echo.Logger 587} 588 589func (c *context) Reset(r *http.Request, w http.ResponseWriter) { 590 c.request = r 591 c.response.reset(w) 592 c.query = nil 593 c.handler = NotFoundHandler 594 c.store = nil 595 c.path = "" 596 c.pnames = nil 597 // NOTE: Don't reset because it has to have length c.echo.maxParam at all times 598 // c.pvalues = nil 599} 600 601