mirror of
https://github.com/kataras/iris.git
synced 2026-01-10 05:25:58 +00:00
Publish the new version ✈️ | Look description please!
# FAQ ### Looking for free support? http://support.iris-go.com https://kataras.rocket.chat/channel/iris ### Looking for previous versions? https://github.com/kataras/iris#version ### Should I upgrade my Iris? Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready. > Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes. **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`. For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework). ### About our new home page http://iris-go.com Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome! [Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him. The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please! Read more at https://github.com/kataras/iris/blob/master/HISTORY.md Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
This commit is contained in:
72
context/configuration.go
Normal file
72
context/configuration.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
type ConfigurationReadOnly interface {
|
||||
// GetVHost returns the non-exported vhost config field.
|
||||
//
|
||||
// If original addr ended with :443 or :80, it will return the host without the port.
|
||||
// If original addr was :https or :http, it will return localhost.
|
||||
// If original addr was 0.0.0.0, it will return localhost.
|
||||
GetVHost() string
|
||||
|
||||
// GetDisablePathCorrection returns the configuration.DisablePathCorrection,
|
||||
// DisablePathCorrection corrects and redirects the requested path to the registered path
|
||||
// for example, if /home/ path is requested but no handler for this Route found,
|
||||
// then the Router checks if /home handler exists, if yes,
|
||||
// (permant)redirects the client to the correct path /home.
|
||||
GetDisablePathCorrection() bool
|
||||
|
||||
// GetEnablePathEscape is the configuration.EnablePathEscape,
|
||||
// returns true when its escapes the path, the named parameters (if any).
|
||||
GetEnablePathEscape() bool
|
||||
|
||||
// GetFireMethodNotAllowed returns the configuration.FireMethodNotAllowed.
|
||||
GetFireMethodNotAllowed() bool
|
||||
// GetDisableBodyConsumptionOnUnmarshal returns the configuration.GetDisableBodyConsumptionOnUnmarshal,
|
||||
// manages the reading behavior of the context's body readers/binders.
|
||||
// If returns true then the body consumption by the `context.UnmarshalBody/ReadJSON/ReadXML`
|
||||
// is disabled.
|
||||
//
|
||||
// By-default io.ReadAll` is used to read the body from the `context.Request.Body which is an `io.ReadCloser`,
|
||||
// if this field setted to true then a new buffer will be created to read from and the request body.
|
||||
// The body will not be changed and existing data before the
|
||||
// context.UnmarshalBody/ReadJSON/ReadXML will be not consumed.
|
||||
GetDisableBodyConsumptionOnUnmarshal() bool
|
||||
|
||||
// GetDisableAutoFireStatusCode returns the configuration.DisableAutoFireStatusCode.
|
||||
// Returns true when the http error status code handler automatic execution turned off.
|
||||
GetDisableAutoFireStatusCode() bool
|
||||
|
||||
// GetTimeFormat returns the configuration.TimeFormat,
|
||||
// format for any kind of datetime parsing.
|
||||
GetTimeFormat() string
|
||||
|
||||
// GetCharset returns the configuration.Charset,
|
||||
// the character encoding for various rendering
|
||||
// used for templates and the rest of the responses.
|
||||
GetCharset() string
|
||||
|
||||
// GetTranslateLanguageContextKey returns the configuration's TranslateFunctionContextKey value,
|
||||
// used for i18n.
|
||||
GetTranslateFunctionContextKey() string
|
||||
|
||||
// GetTranslateLanguageContextKey returns the configuration's TranslateLanguageContextKey value,
|
||||
// used for i18n.
|
||||
GetTranslateLanguageContextKey() string
|
||||
|
||||
// GetViewLayoutContextKey returns the key of the context's user values' key
|
||||
// which is being used to set the template
|
||||
// layout from a middleware or the main handler.
|
||||
// Overrides the parent's or the configuration's.
|
||||
GetViewLayoutContextKey() string
|
||||
// GetViewDataContextKey returns the key of the context's user values' key
|
||||
// which is being used to set the template
|
||||
// binding data from a middleware or the main handler.
|
||||
GetViewDataContextKey() string
|
||||
|
||||
// GetOther returns the configuration.Other map.
|
||||
GetOther() map[string]interface{}
|
||||
}
|
||||
2263
context/context.go
Normal file
2263
context/context.go
Normal file
File diff suppressed because it is too large
Load Diff
51
context/framework.go
Normal file
51
context/framework.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
// Application is the context's available Application instance, only things that are allowed to be happen inside the request are lived here.
|
||||
type Application interface {
|
||||
// ConfigurationReadOnly returns all the available configuration values can be used on a request.
|
||||
ConfigurationReadOnly() ConfigurationReadOnly
|
||||
|
||||
// Log uses the user's defined logger to log a warning or error message.
|
||||
Log(format string, a ...interface{})
|
||||
|
||||
// View executes and write the result of a template file to the writer.
|
||||
//
|
||||
// Use context.View to render templates to the client instead.
|
||||
// Returns an error on failure, otherwise nil.
|
||||
View(writer io.Writer, filename string, layout string, bindingData interface{}) error
|
||||
|
||||
// SessionManager returns the session manager which contain a Start and Destroy methods
|
||||
// used inside the context.Session().
|
||||
//
|
||||
// It's ready to use after the RegisterSessions.
|
||||
SessionManager() (sessions.Sessions, error)
|
||||
|
||||
// ServeHTTPC is the internal router, it's visible because it can be used for advanced use cases,
|
||||
// i.e: routing within a foreign context.
|
||||
//
|
||||
// It is ready to use after Build state.
|
||||
ServeHTTPC(ctx Context)
|
||||
|
||||
// ServeHTTP is the main router handler which calls the .Serve and acquires a new context from the pool.
|
||||
//
|
||||
// It is ready to use after Build state.
|
||||
ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// FireErrorCode executes an error http status code handler
|
||||
// based on the context's status code.
|
||||
//
|
||||
// If a handler is not already registered,
|
||||
// then it creates & registers a new trivial handler on the-fly.
|
||||
FireErrorCode(ctx Context)
|
||||
}
|
||||
135
context/gzip_response_writer.go
Normal file
135
context/gzip_response_writer.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/klauspost/compress/gzip"
|
||||
)
|
||||
|
||||
// compressionPool is a wrapper of sync.Pool, to initialize a new compression writer pool
|
||||
type compressionPool struct {
|
||||
sync.Pool
|
||||
Level int
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// |GZIP raw io.writer, our gzip response writer will use that. |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
// default writer pool with Compressor's level setted to -1
|
||||
var gzipPool = &compressionPool{Level: -1}
|
||||
|
||||
// acquireGzipWriter prepares a gzip writer and returns it.
|
||||
//
|
||||
// see releaseGzipWriter too.
|
||||
func acquireGzipWriter(w io.Writer) *gzip.Writer {
|
||||
v := gzipPool.Get()
|
||||
if v == nil {
|
||||
gzipWriter, err := gzip.NewWriterLevel(w, gzipPool.Level)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return gzipWriter
|
||||
}
|
||||
gzipWriter := v.(*gzip.Writer)
|
||||
gzipWriter.Reset(w)
|
||||
return gzipWriter
|
||||
}
|
||||
|
||||
// releaseGzipWriter called when flush/close and put the gzip writer back to the pool.
|
||||
//
|
||||
// see acquireGzipWriter too.
|
||||
func releaseGzipWriter(gzipWriter *gzip.Writer) {
|
||||
gzipWriter.Close()
|
||||
gzipPool.Put(gzipWriter)
|
||||
}
|
||||
|
||||
// writeGzip writes a compressed form of p to the underlying io.Writer. The
|
||||
// compressed bytes are not necessarily flushed until the Writer is closed.
|
||||
func writeGzip(w io.Writer, b []byte) (int, error) {
|
||||
gzipWriter := acquireGzipWriter(w)
|
||||
n, err := gzipWriter.Write(b)
|
||||
releaseGzipWriter(gzipWriter)
|
||||
return n, err
|
||||
}
|
||||
|
||||
var gzpool = sync.Pool{New: func() interface{} { return &GzipResponseWriter{} }}
|
||||
|
||||
// AcquireGzipResponseWriter returns a new *GzipResponseWriter from the pool.
|
||||
// Releasing is done automatically when request and response is done.
|
||||
func AcquireGzipResponseWriter() *GzipResponseWriter {
|
||||
w := gzpool.Get().(*GzipResponseWriter)
|
||||
return w
|
||||
}
|
||||
|
||||
func releaseGzipResponseWriter(w *GzipResponseWriter) {
|
||||
releaseGzipWriter(w.gzipWriter)
|
||||
gzpool.Put(w)
|
||||
}
|
||||
|
||||
// GzipResponseWriter is an upgraded response writer which writes compressed data to the underline ResponseWriter.
|
||||
//
|
||||
// It's a separate response writer because Iris gives you the ability to "fallback" and "roll-back" the gzip encoding if something
|
||||
// went wrong with the response, and write http errors in plain form instead.
|
||||
type GzipResponseWriter struct {
|
||||
ResponseWriter
|
||||
gzipWriter *gzip.Writer
|
||||
chunks []byte
|
||||
disabled bool
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &GzipResponseWriter{}
|
||||
|
||||
func (w *GzipResponseWriter) BeginGzipResponse(underline ResponseWriter) {
|
||||
w.ResponseWriter = underline
|
||||
w.gzipWriter = acquireGzipWriter(w.ResponseWriter)
|
||||
w.chunks = w.chunks[0:0]
|
||||
w.disabled = false
|
||||
}
|
||||
|
||||
func (w *GzipResponseWriter) EndResponse() {
|
||||
releaseGzipResponseWriter(w)
|
||||
w.ResponseWriter.EndResponse()
|
||||
}
|
||||
|
||||
// Write compresses and writes that data to the underline response writer
|
||||
func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
|
||||
// save the contents to serve them (only gzip data here)
|
||||
w.chunks = append(w.chunks, contents...)
|
||||
return len(w.chunks), nil
|
||||
}
|
||||
|
||||
func (w *GzipResponseWriter) FlushResponse() {
|
||||
if w.disabled {
|
||||
w.ResponseWriter.Write(w.chunks)
|
||||
// remove gzip headers: no need, we just add two of them if gzip was enabled, below
|
||||
// headers := w.ResponseWriter.Header()
|
||||
// headers[contentType] = nil
|
||||
// headers["X-Content-Type-Options"] = nil
|
||||
// headers[varyHeader] = nil
|
||||
// headers[contentEncodingHeader] = nil
|
||||
// headers[contentLength] = nil
|
||||
} else {
|
||||
// if it's not disable write all chunks gzip compressed with the correct response headers.
|
||||
w.ResponseWriter.Header().Add(varyHeaderKey, "Accept-Encoding")
|
||||
w.ResponseWriter.Header().Set(contentEncodingHeaderKey, "gzip")
|
||||
w.gzipWriter.Write(w.chunks) // it writes to the underline ResponseWriter.
|
||||
}
|
||||
w.ResponseWriter.FlushResponse()
|
||||
}
|
||||
|
||||
// ResetBody resets the response body.
|
||||
func (w *GzipResponseWriter) ResetBody() {
|
||||
w.chunks = w.chunks[0:0]
|
||||
}
|
||||
|
||||
// Disable, disables the gzip compression for the next .Write's data,
|
||||
// if called then the contents are being written in plain form.
|
||||
func (w *GzipResponseWriter) Disable() {
|
||||
w.disabled = true
|
||||
}
|
||||
9
context/handler.go
Normal file
9
context/handler.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
type Handler func(Context)
|
||||
|
||||
type Handlers []Handler
|
||||
45
context/pool.go
Normal file
45
context/pool.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Pool is the context pool, it's used inside router and the framework by itself.
|
||||
//
|
||||
// It's the only one real implementation inside this package because it used widely.
|
||||
type Pool struct {
|
||||
pool *sync.Pool
|
||||
newFunc func() Context // we need a field otherwise is not working if we change the return value
|
||||
}
|
||||
|
||||
// New creates and returns a new context pool.
|
||||
func New(newFunc func() Context) *Pool {
|
||||
c := &Pool{pool: &sync.Pool{}, newFunc: newFunc}
|
||||
c.pool.New = func() interface{} { return c.newFunc() }
|
||||
return c
|
||||
}
|
||||
|
||||
// Attach changes the pool's return value Context.
|
||||
func (c *Pool) Attach(newFunc func() Context) {
|
||||
c.newFunc = newFunc
|
||||
}
|
||||
|
||||
// Acquire returns a Context from pool.
|
||||
// See Release.
|
||||
func (c *Pool) Acquire(w http.ResponseWriter, r *http.Request) Context {
|
||||
ctx := c.pool.Get().(Context)
|
||||
ctx.BeginRequest(w, r)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Release puts a Context back to its pull, this function releases its resources.
|
||||
// See Acquire.
|
||||
func (c *Pool) Release(ctx Context) {
|
||||
ctx.EndRequest()
|
||||
c.pool.Put(ctx)
|
||||
}
|
||||
39
context/render_options.go
Normal file
39
context/render_options.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
// +-------------------------------+
|
||||
// | Restful |
|
||||
// +-------------------------------+
|
||||
|
||||
// JSON contains the options for the JSON (Context's) Renderer.
|
||||
type JSON struct {
|
||||
// http-specific
|
||||
StreamingJSON bool
|
||||
// content-specific
|
||||
UnescapeHTML bool
|
||||
Indent string
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// JSONP contains the options for the JSONP (Context's) Renderer.
|
||||
type JSONP struct {
|
||||
// content-specific
|
||||
Indent string
|
||||
Callback string
|
||||
}
|
||||
|
||||
// XML contains the options for the XML (Context's) Renderer.
|
||||
type XML struct {
|
||||
// content-specific
|
||||
Indent string
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// Markdown contains the options for the Markdown (Context's) Renderer.
|
||||
type Markdown struct {
|
||||
// content-specific
|
||||
Sanitize bool
|
||||
}
|
||||
260
context/response_recorder.go
Normal file
260
context/response_recorder.go
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Recorder the middleware to enable response writer recording ( ResponseWriter -> ResponseRecorder)
|
||||
var Recorder = func(ctx Context) {
|
||||
ctx.Record()
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
var rrpool = sync.Pool{New: func() interface{} { return &ResponseRecorder{} }}
|
||||
|
||||
// AcquireResponseRecorder returns a new *AcquireResponseRecorder from the pool.
|
||||
// Releasing is done automatically when request and response is done.
|
||||
func AcquireResponseRecorder() *ResponseRecorder {
|
||||
return rrpool.Get().(*ResponseRecorder)
|
||||
}
|
||||
|
||||
func releaseResponseRecorder(w *ResponseRecorder) {
|
||||
rrpool.Put(w)
|
||||
}
|
||||
|
||||
type ResponseRecorder struct {
|
||||
ResponseWriter
|
||||
// keep track of the body in order to be
|
||||
// resetable and useful inside custom transactions
|
||||
chunks []byte
|
||||
// the saved headers
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &ResponseRecorder{}
|
||||
|
||||
func (w *ResponseRecorder) BeginRecord(underline ResponseWriter) {
|
||||
w.ResponseWriter = underline
|
||||
w.headers = underline.Header()
|
||||
w.ResetBody()
|
||||
}
|
||||
|
||||
func (w *ResponseRecorder) EndResponse() {
|
||||
releaseResponseRecorder(w)
|
||||
w.ResponseWriter.EndResponse()
|
||||
}
|
||||
|
||||
// Write Adds the contents to the body reply, it writes the contents temporarily
|
||||
// to a value in order to be flushed at the end of the request,
|
||||
// this method give us the opportunity to reset the body if needed.
|
||||
//
|
||||
// If WriteHeader has not yet been called, Write calls
|
||||
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
||||
// does not contain a Content-Type line, Write adds a Content-Type set
|
||||
// to the result of passing the initial 512 bytes of written data to
|
||||
// DetectContentType.
|
||||
//
|
||||
// Depending on the HTTP protocol version and the client, calling
|
||||
// Write or WriteHeader may prevent future reads on the
|
||||
// Request.Body. For HTTP/1.x requests, handlers should read any
|
||||
// needed request body data before writing the response. Once the
|
||||
// headers have been flushed (due to either an explicit Flusher.Flush
|
||||
// call or writing enough data to trigger a flush), the request body
|
||||
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
||||
// handlers to continue to read the request body while concurrently
|
||||
// writing the response. However, such behavior may not be supported
|
||||
// by all HTTP/2 clients. Handlers should read before writing if
|
||||
// possible to maximize compatibility.
|
||||
func (w *ResponseRecorder) Write(contents []byte) (int, error) {
|
||||
w.chunks = append(w.chunks, contents...)
|
||||
return len(w.chunks), nil
|
||||
}
|
||||
|
||||
// Writef formats according to a format specifier and writes to the response.
|
||||
//
|
||||
// Returns the number of bytes written and any write error encountered.
|
||||
func (w *ResponseRecorder) Writef(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// WriteString writes a simple string to the response.
|
||||
//
|
||||
// Returns the number of bytes written and any write error encountered
|
||||
func (w *ResponseRecorder) WriteString(s string) (n int, err error) {
|
||||
return w.Write([]byte(s))
|
||||
}
|
||||
|
||||
// SetBody overrides the body and sets it to a slice of bytes value.
|
||||
func (w *ResponseRecorder) SetBody(b []byte) {
|
||||
w.chunks = b
|
||||
}
|
||||
|
||||
// SetBodyString overrides the body and sets it to a string value.
|
||||
func (w *ResponseRecorder) SetBodyString(s string) {
|
||||
w.SetBody([]byte(s))
|
||||
}
|
||||
|
||||
// Body returns the body tracked from the writer so far
|
||||
// do not use this for edit.
|
||||
func (w *ResponseRecorder) Body() []byte {
|
||||
return w.chunks
|
||||
}
|
||||
|
||||
// ResetBody resets the response body.
|
||||
func (w *ResponseRecorder) ResetBody() {
|
||||
w.chunks = w.chunks[0:0]
|
||||
}
|
||||
|
||||
// ResetHeaders sets the headers to the underline's response writer's headers, may empty.
|
||||
func (w *ResponseRecorder) ResetHeaders() {
|
||||
w.headers = w.ResponseWriter.Header()
|
||||
}
|
||||
|
||||
// ClearHeaders clears all headers, both temp and underline's response writer.
|
||||
func (w *ResponseRecorder) ClearHeaders() {
|
||||
w.headers = http.Header{}
|
||||
h := w.ResponseWriter.Header()
|
||||
for k := range h {
|
||||
h[k] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the response body, headers and the status code header.
|
||||
func (w *ResponseRecorder) Reset() {
|
||||
w.ClearHeaders()
|
||||
w.WriteHeader(defaultStatusCode)
|
||||
w.ResetBody()
|
||||
}
|
||||
|
||||
// FlushResponse the full body, headers and status code to the underline response writer
|
||||
// called automatically at the end of each request.
|
||||
func (w *ResponseRecorder) FlushResponse() {
|
||||
// copy the headers to the underline response writer
|
||||
if w.headers != nil {
|
||||
h := w.ResponseWriter.Header()
|
||||
|
||||
for k, values := range w.headers {
|
||||
h[k] = nil
|
||||
for i := range values {
|
||||
h.Add(k, values[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: before the ResponseWriter.Write in order to:
|
||||
// set the given status code even if the body is empty.
|
||||
w.ResponseWriter.FlushResponse()
|
||||
|
||||
if len(w.chunks) > 0 {
|
||||
// ignore error
|
||||
w.ResponseWriter.Write(w.chunks)
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a clone of this response writer
|
||||
// it copies the header, status code, headers and the beforeFlush finally returns a new ResponseRecorder
|
||||
func (w *ResponseRecorder) Clone() ResponseWriter {
|
||||
wc := &ResponseRecorder{}
|
||||
wc.headers = w.headers
|
||||
wc.chunks = w.chunks[0:]
|
||||
if resW, ok := w.ResponseWriter.(*responseWriter); ok {
|
||||
wc.ResponseWriter = &(*resW) // clone it
|
||||
} else { // else just copy, may pointer, developer can change its behavior
|
||||
wc.ResponseWriter = w.ResponseWriter
|
||||
}
|
||||
|
||||
return wc
|
||||
}
|
||||
|
||||
// WriteTo writes a response writer (temp: status code, headers and body) to another response writer
|
||||
func (w *ResponseRecorder) WriteTo(res ResponseWriter) {
|
||||
|
||||
if to, ok := res.(*ResponseRecorder); ok {
|
||||
|
||||
// set the status code, to is first ( probably an error >=400)
|
||||
if statusCode := w.ResponseWriter.StatusCode(); statusCode == defaultStatusCode {
|
||||
to.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
if beforeFlush := w.ResponseWriter.GetBeforeFlush(); beforeFlush != nil {
|
||||
// if to had a before flush, lets combine them
|
||||
if to.GetBeforeFlush() != nil {
|
||||
nextBeforeFlush := beforeFlush
|
||||
prevBeforeFlush := to.GetBeforeFlush()
|
||||
to.SetBeforeFlush(func() {
|
||||
prevBeforeFlush()
|
||||
nextBeforeFlush()
|
||||
})
|
||||
} else {
|
||||
to.SetBeforeFlush(w.ResponseWriter.GetBeforeFlush())
|
||||
}
|
||||
}
|
||||
|
||||
// if "to" is *responseWriter and it never written before (if -1),
|
||||
// set the "w"'s written length.
|
||||
if resW, ok := to.ResponseWriter.(*responseWriter); ok {
|
||||
if resW.Written() != StatusCodeWritten {
|
||||
resW.written = w.ResponseWriter.Written()
|
||||
}
|
||||
}
|
||||
|
||||
// append the headers
|
||||
if w.headers != nil {
|
||||
for k, values := range w.headers {
|
||||
for _, v := range values {
|
||||
if to.headers.Get(v) == "" {
|
||||
to.headers.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append the body
|
||||
if len(w.chunks) > 0 {
|
||||
// ignore error
|
||||
to.Write(w.chunks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *ResponseRecorder) Flush() {
|
||||
w.ResponseWriter.Flush()
|
||||
w.ResetBody()
|
||||
}
|
||||
|
||||
// Push initiates an HTTP/2 server push. This constructs a synthetic
|
||||
// request using the given target and options, serializes that request
|
||||
// into a PUSH_PROMISE frame, then dispatches that request using the
|
||||
// server's request handler. If opts is nil, default options are used.
|
||||
//
|
||||
// The target must either be an absolute path (like "/path") or an absolute
|
||||
// URL that contains a valid host and the same scheme as the parent request.
|
||||
// If the target is a path, it will inherit the scheme and host of the
|
||||
// parent request.
|
||||
//
|
||||
// The HTTP/2 spec disallows recursive pushes and cross-authority pushes.
|
||||
// Push may or may not detect these invalid pushes; however, invalid
|
||||
// pushes will be detected and canceled by conforming clients.
|
||||
//
|
||||
// Handlers that wish to push URL X should call Push before sending any
|
||||
// data that may trigger a request for URL X. This avoids a race where the
|
||||
// client issues requests for X before receiving the PUSH_PROMISE for X.
|
||||
//
|
||||
// Push returns ErrPushNotSupported if the client has disabled push or if push
|
||||
// is not supported on the underlying connection.
|
||||
func (w *ResponseRecorder) Push(target string, opts *http.PushOptions) error {
|
||||
w.FlushResponse()
|
||||
err := w.ResponseWriter.Push(target, opts)
|
||||
// NOTE: we have to reset them even if the push failed.
|
||||
w.ResetBody()
|
||||
w.ResetHeaders()
|
||||
|
||||
return err
|
||||
}
|
||||
363
context/response_writer.go
Normal file
363
context/response_writer.go
Normal file
@@ -0,0 +1,363 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
// ResponseWriter interface is used by the context to serve an HTTP handler to
|
||||
// construct an HTTP response.
|
||||
//
|
||||
// Note: Only this ResponseWriter is an interface in order to be able
|
||||
// for developers to change the response writer of the Context via `context.ResetResponseWriter`.
|
||||
// The rest of the response writers implementations (ResponseRecorder & GzipResponseWriter) are coupled to the internal
|
||||
// ResponseWriter implementation(*responseWriter).
|
||||
//
|
||||
// A ResponseWriter may not be used after the Handler
|
||||
// has returned.
|
||||
type ResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
http.CloseNotifier
|
||||
http.Pusher
|
||||
|
||||
// BeginResponse receives an http.ResponseWriter
|
||||
// and initialize or reset the response writer's field's values.
|
||||
BeginResponse(http.ResponseWriter)
|
||||
|
||||
// EndResponse is the last function which is called right before the server sent the final response.
|
||||
//
|
||||
// Here is the place which we can make the last checks or do a cleanup.
|
||||
EndResponse()
|
||||
|
||||
// Writef formats according to a format specifier and writes to the response.
|
||||
//
|
||||
// Returns the number of bytes written and any write error encountered.
|
||||
Writef(format string, a ...interface{}) (n int, err error)
|
||||
|
||||
// WriteString writes a simple string to the response.
|
||||
//
|
||||
// Returns the number of bytes written and any write error encountered.
|
||||
WriteString(s string) (n int, err error)
|
||||
|
||||
// StatusCode returns the status code header value.
|
||||
StatusCode() int
|
||||
|
||||
// Written should returns the total length of bytes that were being written to the client.
|
||||
// In addition Iris provides some variables to help low-level actions:
|
||||
// NoWritten, means that nothing were written yet and the response writer is still live.
|
||||
// StatusCodeWritten, means that status code were written but no other bytes are written to the client, response writer may closed.
|
||||
// > 0 means that the reply was written and it's the total number of bytes were written.
|
||||
Written() int
|
||||
|
||||
// SetBeforeFlush registers the unique callback which called exactly before the response is flushed to the client.
|
||||
SetBeforeFlush(cb func())
|
||||
// GetBeforeFlush returns (not execute) the before flush callback, or nil if not setted by SetBeforeFlush.
|
||||
GetBeforeFlush() func()
|
||||
// FlushResponse should be called only once before EndResponse.
|
||||
// it tries to send the status code if not sent already
|
||||
// and calls the before flush callback, if any.
|
||||
//
|
||||
// FlushResponse can be called before EndResponse, but it should
|
||||
// be the last call of this response writer.
|
||||
FlushResponse()
|
||||
|
||||
// clone returns a clone of this response writer
|
||||
// it copies the header, status code, headers and the beforeFlush finally returns a new ResponseRecorder.
|
||||
Clone() ResponseWriter
|
||||
|
||||
// WiteTo writes a response writer (temp: status code, headers and body) to another response writer
|
||||
WriteTo(ResponseWriter)
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Response Writer Implementation |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
var rpool = sync.Pool{New: func() interface{} { return &responseWriter{} }}
|
||||
|
||||
// AcquireResponseWriter returns a new *ResponseWriter from the pool.
|
||||
// Releasing is done automatically when request and response is done.
|
||||
func AcquireResponseWriter() ResponseWriter {
|
||||
return rpool.Get().(*responseWriter)
|
||||
}
|
||||
|
||||
func releaseResponseWriter(w ResponseWriter) {
|
||||
rpool.Put(w)
|
||||
}
|
||||
|
||||
// ResponseWriter is the basic response writer,
|
||||
// it writes directly to the underline http.ResponseWriter
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int // the saved status code which will be used from the cache service
|
||||
// statusCodeSent bool // reply header has been (logically) written | no needed any more as we have a variable to catch total len of written bytes
|
||||
written int // the total size of bytes were written
|
||||
// yes only one callback, we need simplicity here because on FireStatusCode the beforeFlush events should NOT be cleared
|
||||
// but the response is cleared.
|
||||
// Sometimes is useful to keep the event,
|
||||
// so we keep one func only and let the user decide when he/she wants to override it with an empty func before the FireStatusCode (context's behavior)
|
||||
beforeFlush func()
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
|
||||
const (
|
||||
defaultStatusCode = http.StatusOK
|
||||
// NoWritten !=-1 => when nothing written before
|
||||
NoWritten = -1
|
||||
// StatusCodeWritten != 0 => when only status code written
|
||||
StatusCodeWritten = 0
|
||||
)
|
||||
|
||||
// BeginResponse receives an http.ResponseWriter
|
||||
// and initialize or reset the response writer's field's values.
|
||||
func (w *responseWriter) BeginResponse(underline http.ResponseWriter) {
|
||||
w.beforeFlush = nil
|
||||
w.written = NoWritten
|
||||
w.statusCode = defaultStatusCode
|
||||
w.ResponseWriter = underline
|
||||
}
|
||||
|
||||
// EndResponse is the last function which is called right before the server sent the final response.
|
||||
//
|
||||
// Here is the place which we can make the last checks or do a cleanup.
|
||||
func (w *responseWriter) EndResponse() {
|
||||
releaseResponseWriter(w)
|
||||
}
|
||||
|
||||
// Written should returns the total length of bytes that were being written to the client.
|
||||
// In addition Iris provides some variables to help low-level actions:
|
||||
// NoWritten, means that nothing were written yet and the response writer is still live.
|
||||
// StatusCodeWritten, means that status code were written but no other bytes are written to the client, response writer may closed.
|
||||
// > 0 means that the reply was written and it's the total number of bytes were written.
|
||||
func (w *responseWriter) Written() int {
|
||||
return w.written
|
||||
}
|
||||
|
||||
// prin to write na benei to write header
|
||||
// meta to write den ginete edw
|
||||
// prepei omws kai mono me WriteHeader kai xwris Write na pigenei to status code
|
||||
// ara...wtf prepei na exw function flushStatusCode kai na elenxei an exei dw9ei status code na to kanei write aliws 200
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code.
|
||||
// If WriteHeader is not called explicitly, the first call to Write
|
||||
// will trigger an implicit WriteHeader(http.StatusOK).
|
||||
// Thus explicit calls to WriteHeader are mainly used to
|
||||
// send error codes.
|
||||
func (w *responseWriter) WriteHeader(statusCode int) {
|
||||
w.statusCode = statusCode
|
||||
}
|
||||
|
||||
func (w *responseWriter) tryWriteHeader() {
|
||||
if w.written == NoWritten { // before write, once.
|
||||
w.written = StatusCodeWritten
|
||||
w.ResponseWriter.WriteHeader(w.statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes to the client
|
||||
// If WriteHeader has not yet been called, Write calls
|
||||
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
||||
// does not contain a Content-Type line, Write adds a Content-Type set
|
||||
// to the result of passing the initial 512 bytes of written data to
|
||||
// DetectContentType.
|
||||
//
|
||||
// Depending on the HTTP protocol version and the client, calling
|
||||
// Write or WriteHeader may prevent future reads on the
|
||||
// Request.Body. For HTTP/1.x requests, handlers should read any
|
||||
// needed request body data before writing the response. Once the
|
||||
// headers have been flushed (due to either an explicit Flusher.Flush
|
||||
// call or writing enough data to trigger a flush), the request body
|
||||
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
||||
// handlers to continue to read the request body while concurrently
|
||||
// writing the response. However, such behavior may not be supported
|
||||
// by all HTTP/2 clients. Handlers should read before writing if
|
||||
// possible to maximize compatibility.
|
||||
func (w *responseWriter) Write(contents []byte) (int, error) {
|
||||
w.tryWriteHeader()
|
||||
n, err := w.ResponseWriter.Write(contents)
|
||||
w.written += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Writef formats according to a format specifier and writes to the response.
|
||||
//
|
||||
// Returns the number of bytes written and any write error encountered.
|
||||
func (w *responseWriter) Writef(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// WriteString writes a simple string to the response.
|
||||
//
|
||||
// Returns the number of bytes written and any write error encountered.
|
||||
func (w *responseWriter) WriteString(s string) (int, error) {
|
||||
w.tryWriteHeader()
|
||||
n, err := io.WriteString(w.ResponseWriter, s)
|
||||
w.written += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
// StatusCode returns the status code header value
|
||||
func (w *responseWriter) StatusCode() int {
|
||||
return w.statusCode
|
||||
}
|
||||
|
||||
func (w *responseWriter) GetBeforeFlush() func() {
|
||||
return w.beforeFlush
|
||||
}
|
||||
|
||||
// SetBeforeFlush registers the unique callback which called exactly before the response is flushed to the client
|
||||
func (w *responseWriter) SetBeforeFlush(cb func()) {
|
||||
w.beforeFlush = cb
|
||||
}
|
||||
|
||||
func (w *responseWriter) FlushResponse() {
|
||||
if w.beforeFlush != nil {
|
||||
w.beforeFlush()
|
||||
}
|
||||
|
||||
w.tryWriteHeader()
|
||||
}
|
||||
|
||||
// Clone returns a clone of this response writer
|
||||
// it copies the header, status code, headers and the beforeFlush finally returns a new ResponseRecorder.
|
||||
func (w *responseWriter) Clone() ResponseWriter {
|
||||
wc := &responseWriter{}
|
||||
wc.ResponseWriter = w.ResponseWriter
|
||||
wc.statusCode = w.statusCode
|
||||
wc.beforeFlush = w.beforeFlush
|
||||
wc.written = w.written
|
||||
return wc
|
||||
}
|
||||
|
||||
// WriteTo writes a response writer (temp: status code, headers and body) to another response writer.
|
||||
func (w *responseWriter) WriteTo(to ResponseWriter) {
|
||||
// set the status code, failure status code are first class
|
||||
if w.statusCode >= 400 {
|
||||
to.WriteHeader(w.statusCode)
|
||||
}
|
||||
|
||||
// append the headers
|
||||
if w.Header() != nil {
|
||||
for k, values := range w.Header() {
|
||||
for _, v := range values {
|
||||
if to.Header().Get(v) == "" {
|
||||
to.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// the body is not copied, this writer doesn't support recording
|
||||
}
|
||||
|
||||
// Hijack lets the caller take over the connection.
|
||||
// After a call to Hijack(), the HTTP server library
|
||||
// will not do anything else with the connection.
|
||||
//
|
||||
// It becomes the caller's responsibility to manage
|
||||
// and close the connection.
|
||||
//
|
||||
// The returned net.Conn may have read or write deadlines
|
||||
// already set, depending on the configuration of the
|
||||
// Server. It is the caller's responsibility to set
|
||||
// or clear those deadlines as needed.
|
||||
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if h, isHijacker := w.ResponseWriter.(http.Hijacker); isHijacker {
|
||||
w.written = StatusCodeWritten
|
||||
return h.Hijack()
|
||||
}
|
||||
|
||||
return nil, nil, errors.New("hijack is not supported by this ResponseWriter")
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *responseWriter) Flush() {
|
||||
// The Flusher interface is implemented by ResponseWriters that allow
|
||||
// an HTTP handler to flush buffered data to the client.
|
||||
//
|
||||
// The default HTTP/1.x and HTTP/2 ResponseWriter implementations
|
||||
// support Flusher, but ResponseWriter wrappers may not. Handlers
|
||||
// should always test for this ability at runtime.
|
||||
//
|
||||
// Note that even for ResponseWriters that support Flush,
|
||||
// if the client is connected through an HTTP proxy,
|
||||
// the buffered data may not reach the client until the response
|
||||
// completes.
|
||||
if fl, isFlusher := w.ResponseWriter.(http.Flusher); isFlusher {
|
||||
fl.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// ErrPushNotSupported is returned by the Push method to
|
||||
// indicate that HTTP/2 Push support is not available.
|
||||
var ErrPushNotSupported = errors.New("push feature is not supported by this ResponseWriter")
|
||||
|
||||
// Push initiates an HTTP/2 server push. This constructs a synthetic
|
||||
// request using the given target and options, serializes that request
|
||||
// into a PUSH_PROMISE frame, then dispatches that request using the
|
||||
// server's request handler. If opts is nil, default options are used.
|
||||
//
|
||||
// The target must either be an absolute path (like "/path") or an absolute
|
||||
// URL that contains a valid host and the same scheme as the parent request.
|
||||
// If the target is a path, it will inherit the scheme and host of the
|
||||
// parent request.
|
||||
//
|
||||
// The HTTP/2 spec disallows recursive pushes and cross-authority pushes.
|
||||
// Push may or may not detect these invalid pushes; however, invalid
|
||||
// pushes will be detected and canceled by conforming clients.
|
||||
//
|
||||
// Handlers that wish to push URL X should call Push before sending any
|
||||
// data that may trigger a request for URL X. This avoids a race where the
|
||||
// client issues requests for X before receiving the PUSH_PROMISE for X.
|
||||
//
|
||||
// Push returns ErrPushNotSupported if the client has disabled push or if push
|
||||
// is not supported on the underlying connection.
|
||||
func (w *responseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if pusher, isPusher := w.ResponseWriter.(http.Pusher); isPusher {
|
||||
err := pusher.Push(target, opts)
|
||||
if err != nil && err.Error() == http.ErrNotSupported.ErrorString {
|
||||
return ErrPushNotSupported
|
||||
}
|
||||
return err
|
||||
}
|
||||
return ErrPushNotSupported
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone
|
||||
// away.
|
||||
//
|
||||
// CloseNotify may wait to notify until Request.Body has been
|
||||
// fully read.
|
||||
//
|
||||
// After the Handler has returned, there is no guarantee
|
||||
// that the channel receives a value.
|
||||
//
|
||||
// If the protocol is HTTP/1.1 and CloseNotify is called while
|
||||
// processing an idempotent request (such a GET) while
|
||||
// HTTP/1.1 pipelining is in use, the arrival of a subsequent
|
||||
// pipelined request may cause a value to be sent on the
|
||||
// returned channel. In practice HTTP/1.1 pipelining is not
|
||||
// enabled in browsers and not seen often in the wild. If this
|
||||
// is a problem, use HTTP/2 or only use CloseNotify on methods
|
||||
// such as POST.
|
||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
if notifier, supportsCloseNotify := w.ResponseWriter.(http.CloseNotifier); supportsCloseNotify {
|
||||
return notifier.CloseNotify()
|
||||
}
|
||||
ch := make(chan bool, 1)
|
||||
return ch
|
||||
}
|
||||
184
context/transaction.go
Normal file
184
context/transaction.go
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
// TransactionErrResult could be named also something like 'MaybeError',
|
||||
// it is useful to send it on transaction.Complete in order to execute a custom error mesasge to the user.
|
||||
//
|
||||
// in simple words it's just a 'traveler message' between the transaction and its scope.
|
||||
// it is totally optional
|
||||
type TransactionErrResult struct {
|
||||
StatusCode int
|
||||
// if reason is empty then the already relative registered (custom or not)
|
||||
// error will be executed if the scope allows that.
|
||||
Reason string
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// Error returns the reason given by the user or an empty string
|
||||
func (err TransactionErrResult) Error() string {
|
||||
return err.Reason
|
||||
}
|
||||
|
||||
// IsFailure returns true if this is an actual error
|
||||
func (err TransactionErrResult) IsFailure() bool {
|
||||
return err.StatusCode >= 400
|
||||
}
|
||||
|
||||
// NewTransactionErrResult returns a new transaction result with the given error message,
|
||||
// it can be empty too, but if not then the transaction's scope is decided what to do with that
|
||||
func NewTransactionErrResult() TransactionErrResult {
|
||||
return TransactionErrResult{}
|
||||
}
|
||||
|
||||
// TransactionScope is the manager of the transaction's response, can be resseted and skipped
|
||||
// from its parent context or execute an error or skip other transactions
|
||||
type TransactionScope interface {
|
||||
// EndTransaction returns if can continue to the next transactions or not (false)
|
||||
// called after Complete, empty or not empty error
|
||||
EndTransaction(maybeErr TransactionErrResult, ctx Context) bool
|
||||
}
|
||||
|
||||
// TransactionScopeFunc the transaction's scope signature
|
||||
type TransactionScopeFunc func(maybeErr TransactionErrResult, ctx Context) bool
|
||||
|
||||
// EndTransaction ends the transaction with a callback to itself, implements the TransactionScope interface
|
||||
func (tsf TransactionScopeFunc) EndTransaction(maybeErr TransactionErrResult, ctx Context) bool {
|
||||
return tsf(maybeErr, ctx)
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Transaction Implementation |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
// Transaction gives the users the opportunity to code their route handlers cleaner and safier
|
||||
// it receives a scope which is decided when to send an error to the user, recover from panics
|
||||
// stop the execution of the next transactions and so on...
|
||||
//
|
||||
// it's default scope is the TransientTransactionScope which is silently
|
||||
// skips the current transaction's response if transaction.Complete accepts a non-empty error.
|
||||
//
|
||||
// Create and set custom transactions scopes with transaction.SetScope.
|
||||
//
|
||||
// For more information please visit the tests.
|
||||
type Transaction struct {
|
||||
context Context
|
||||
parent Context
|
||||
hasError bool
|
||||
scope TransactionScope
|
||||
}
|
||||
|
||||
func newTransaction(from *context) *Transaction {
|
||||
tempCtx := *from
|
||||
writer := tempCtx.ResponseWriter().Clone()
|
||||
tempCtx.ResetResponseWriter(writer)
|
||||
t := &Transaction{
|
||||
parent: from,
|
||||
context: &tempCtx,
|
||||
scope: TransientTransactionScope,
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Context returns the current context of the transaction.
|
||||
func (t *Transaction) Context() Context {
|
||||
return t.context
|
||||
}
|
||||
|
||||
// SetScope sets the current transaction's scope
|
||||
// iris.RequestTransactionScope || iris.TransientTransactionScope (default).
|
||||
func (t *Transaction) SetScope(scope TransactionScope) {
|
||||
t.scope = scope
|
||||
}
|
||||
|
||||
// Complete completes the transaction
|
||||
// rollback and send an error when the error is not empty.
|
||||
// The next steps depends on its Scope.
|
||||
//
|
||||
// The error can be a type of context.NewTransactionErrResult().
|
||||
func (t *Transaction) Complete(err error) {
|
||||
maybeErr := TransactionErrResult{}
|
||||
|
||||
if err != nil {
|
||||
t.hasError = true
|
||||
|
||||
statusCode := 504 // bad request
|
||||
reason := err.Error()
|
||||
cType := "text/plain; charset=" + t.context.Application().ConfigurationReadOnly().GetCharset()
|
||||
|
||||
if errWstatus, ok := err.(TransactionErrResult); ok {
|
||||
if errWstatus.StatusCode > 0 {
|
||||
statusCode = errWstatus.StatusCode
|
||||
}
|
||||
|
||||
if errWstatus.Reason != "" {
|
||||
reason = errWstatus.Reason
|
||||
}
|
||||
// get the content type used on this transaction
|
||||
if cTypeH := t.context.ResponseWriter().Header().Get(contentTypeHeaderKey); cTypeH != "" {
|
||||
cType = cTypeH
|
||||
}
|
||||
|
||||
}
|
||||
maybeErr.StatusCode = statusCode
|
||||
maybeErr.Reason = reason
|
||||
maybeErr.ContentType = cType
|
||||
}
|
||||
// the transaction ends with error or not error, it decides what to do next with its Response
|
||||
// the Response is appended to the parent context an all cases but it checks for empty body,headers and all that,
|
||||
// if they are empty (silent error or not error at all)
|
||||
// then all transaction's actions are skipped as expected
|
||||
canContinue := t.scope.EndTransaction(maybeErr, t.context)
|
||||
if !canContinue {
|
||||
t.parent.SkipTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
// TransientTransactionScope explanation:
|
||||
//
|
||||
// independent 'silent' scope, if transaction fails (if transaction.IsFailure() == true)
|
||||
// then its response is not written to the real context no error is provided to the user.
|
||||
// useful for the most cases.
|
||||
var TransientTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx Context) bool {
|
||||
if maybeErr.IsFailure() {
|
||||
ctx.Recorder().Reset() // this response is skipped because it's empty.
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// RequestTransactionScope explanation:
|
||||
//
|
||||
// if scope fails (if transaction.IsFailure() == true)
|
||||
// then the rest of the context's response (transaction or normal flow)
|
||||
// is not written to the client, and an error status code is written instead.
|
||||
var RequestTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx Context) bool {
|
||||
if maybeErr.IsFailure() {
|
||||
|
||||
// we need to register a beforeResponseFlush event here in order
|
||||
// to execute last the FireStatusCode
|
||||
// (which will reset the whole response's body, status code and headers setted from normal flow or other transactions too)
|
||||
ctx.ResponseWriter().SetBeforeFlush(func() {
|
||||
// we need to re-take the context's response writer
|
||||
// because inside here the response writer is changed to the original's
|
||||
// look ~context:1306
|
||||
w := ctx.ResponseWriter().(*ResponseRecorder)
|
||||
if maybeErr.Reason != "" {
|
||||
// send the error with the info user provided
|
||||
w.SetBodyString(maybeErr.Reason)
|
||||
w.WriteHeader(maybeErr.StatusCode)
|
||||
ctx.ContentType(maybeErr.ContentType)
|
||||
} else {
|
||||
// else execute the registered user error and skip the next transactions and all normal flow,
|
||||
ctx.StatusCode(maybeErr.StatusCode)
|
||||
ctx.StopExecution()
|
||||
}
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
193
context/values.go
Normal file
193
context/values.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
// RequestValue is the entry of the context storage RequestValues - .Values()
|
||||
RequestValue struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// RequestValues is just a key-value storage which context's request values should implement.
|
||||
RequestValues []RequestValue
|
||||
)
|
||||
|
||||
// RequestValuesReadOnly the request values with read-only access.
|
||||
type RequestValuesReadOnly struct {
|
||||
RequestValues
|
||||
}
|
||||
|
||||
// Set does nothing.
|
||||
func (r RequestValuesReadOnly) Set(string, interface{}) {}
|
||||
|
||||
// Set sets a value to the key-value context storage, can be familiar as "User Values".
|
||||
func (r *RequestValues) Set(key string, value interface{}) {
|
||||
args := *r
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.key == key {
|
||||
kv.value = value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c := cap(args)
|
||||
if c > n {
|
||||
args = args[:n+1]
|
||||
kv := &args[n]
|
||||
kv.key = key
|
||||
kv.value = value
|
||||
*r = args
|
||||
return
|
||||
}
|
||||
|
||||
kv := RequestValue{}
|
||||
kv.key = key
|
||||
kv.value = value
|
||||
*r = append(args, kv)
|
||||
}
|
||||
|
||||
// Get returns the user's value based on its key.
|
||||
func (r *RequestValues) Get(key string) interface{} {
|
||||
args := *r
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.key == key {
|
||||
return kv.value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visit accepts a visitor which will be filled
|
||||
// by the key-value objects, the caller should not try to change the underline values.
|
||||
func (r *RequestValues) Visit(visitor func(key string, value interface{})) {
|
||||
args := *r
|
||||
for i, n := 0, len(args); i < n; i++ {
|
||||
visitor(args[i].key, args[i].value)
|
||||
}
|
||||
}
|
||||
|
||||
// GetString returns the user's value as string, based on its key.
|
||||
func (r *RequestValues) GetString(key string) string {
|
||||
if v, ok := r.Get(key).(string); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
var errIntParse = errors.New("unable to find or parse the integer, found: %#v")
|
||||
|
||||
// GetInt returns the user's value as int, based on its key.
|
||||
func (r *RequestValues) GetInt(key string) (int, error) {
|
||||
v := r.Get(key)
|
||||
if vint, ok := v.(int); ok {
|
||||
return vint, nil
|
||||
} else if vstring, sok := v.(string); sok {
|
||||
return strconv.Atoi(vstring)
|
||||
}
|
||||
|
||||
return -1, errIntParse.Format(v)
|
||||
}
|
||||
|
||||
// GetInt64 returns the user's value as int64, based on its key.
|
||||
func (r *RequestValues) GetInt64(key string) (int64, error) {
|
||||
return strconv.ParseInt(r.GetString(key), 10, 64)
|
||||
}
|
||||
|
||||
// Reset clears all the request values.
|
||||
func (r *RequestValues) Reset() {
|
||||
*r = (*r)[0:0]
|
||||
}
|
||||
|
||||
// ReadOnly returns a new request values with read-only access.
|
||||
func (r *RequestValues) ReadOnly() RequestValuesReadOnly {
|
||||
args := *r
|
||||
values := make(RequestValues, len(args))
|
||||
copy(values, args)
|
||||
return RequestValuesReadOnly{values}
|
||||
}
|
||||
|
||||
// Len returns the full length of the values.
|
||||
func (r *RequestValues) Len() int {
|
||||
args := *r
|
||||
return len(args)
|
||||
}
|
||||
|
||||
// RequestParam is the entry of RequestParams, request's url named parameters are storaged here.
|
||||
type RequestParam struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// RequestParams is a key string - value string storage which context's request params should implement.
|
||||
// RequestValues is for communication between middleware, RequestParams cannot be changed, are setted at the routing
|
||||
// time, stores the dynamic named parameters, can be empty if the route is static.
|
||||
type RequestParams []RequestParam
|
||||
|
||||
// Get returns the param's value based on its key.
|
||||
func (r RequestParams) Get(key string) string {
|
||||
for _, p := range r {
|
||||
if p.Key == key {
|
||||
return p.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Visit accepts a visitor which will be filled
|
||||
// by the key and value.
|
||||
// The caller should not try to change the underline values.
|
||||
func (r RequestParams) Visit(visitor func(key string, value string)) {
|
||||
for i, n := 0, len(r); i < n; i++ {
|
||||
visitor(r[i].Key, r[i].Value)
|
||||
}
|
||||
}
|
||||
|
||||
// GetInt returns the user's value as int, based on its key.
|
||||
func (r RequestParams) GetInt(key string) (int, error) {
|
||||
v := r.Get(key)
|
||||
return strconv.Atoi(v)
|
||||
}
|
||||
|
||||
// GetInt64 returns the user's value as int64, based on its key.
|
||||
func (r RequestParams) GetInt64(key string) (int64, error) {
|
||||
return strconv.ParseInt(r.Get(key), 10, 64)
|
||||
}
|
||||
|
||||
// GetDecoded returns the url-query-decoded user's value based on its key.
|
||||
func (r RequestParams) GetDecoded(key string) string {
|
||||
return DecodeQuery(DecodeQuery(r.Get(key)))
|
||||
}
|
||||
|
||||
// GetIntUnslashed same as Get but it removes the first slash if found.
|
||||
// Usage: Get an id from a wildcard path.
|
||||
//
|
||||
// Returns -1 with an error if the parameter couldn't be found.
|
||||
func (r RequestParams) GetIntUnslashed(key string) (int, error) {
|
||||
v := r.Get(key)
|
||||
if v != "" {
|
||||
if len(v) > 1 {
|
||||
if v[0] == '/' {
|
||||
v = v[1:]
|
||||
}
|
||||
}
|
||||
return strconv.Atoi(v)
|
||||
|
||||
}
|
||||
|
||||
return -1, errIntParse.Format(v)
|
||||
}
|
||||
|
||||
// Len returns the full length of the params.
|
||||
func (r RequestParams) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
Reference in New Issue
Block a user