Permalink
Browse files

Update to 6.0.1 - use the response recorder whenever you need it. Rea…

…d HISTORY.md
  • Loading branch information...
1 parent 8bbd9f8 commit 058d70e53387ec66e28d4aa5273da5ca865e883e @kataras committed Jan 4, 2017
Showing with 530 additions and 209 deletions.
  1. +20 −0 HISTORY.md
  2. +2 −2 README.md
  3. +46 −45 context.go
  4. +3 −2 context_test.go
  5. +22 −8 http.go
  6. +3 −3 http_test.go
  7. +14 −10 iris.go
  8. +221 −0 response_recorder.go
  9. +122 −131 response_writer.go
  10. +65 −0 response_writer_test.go
  11. +10 −6 transactions.go
  12. +2 −2 webfs.go
View
@@ -2,6 +2,26 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
+## 6.0.0 -> 6.0.1
+
+We had(for 2 days) one ResponseWriter which has special and unique features, but it slowed the execution a little bit, so I had to think more about it, I want to keep iris as the fastest http/2 web framework, well-designed and also to be usable and very easy for new programmers, performance vs design is tough decision. I choose performance most of the times but golang gives us the way to have a good design with that too.
+
+I had to convert that ResponseWriter to a 'big but simple golang' interface and split the behavior into two parts, one will be the default and fast response writer, and the other will be the most useful response writer + transactions = iris.ResponseRecorder (no other framework or library have these features as far as I know). At the same time I had to provide an easy one-call way to wrap the basic response writer to a response recorder and set it to the context or to the whole app.
+
+> Of course I give the green light to other authors to copy these response writers as I already did with the whole source code and I'm happy to see my code exists into other famous web frameworks even when they don't notice my name anywhere :)
+
+- **response_writer.go**: is the response writer as you knew it with iris' bonus like the `StatusCode() int` which returns the http status code (useful for middleware which needs to know the previous status code), `WriteHeader` which doesn't let you write the status code more than once and so on.
+
+- **response_recorder.go**: is the response writer used by `Transactions` but you can use it by calling the `context.Record/Redorder/IsRecording()`. It lets you `ResetBody` , `ResetHeaders and cookies`, set the `status code` at any time (before or after its Write method) and more.
+
+
+Transform the responseWriter to a ResponseRecorder is ridiculous easily, depending on yours preferences select one of these methods:
+
+- context call (lifetime only inside route's handlers/middleware): `context.Record();` which will convert the context.ResponseWriter to a ResponseRecorder. All previous methods works as before but if you want to `ResetBody/Reset/ResetHeaders/SetBody/SetBodyString` you will have to use the `w := context.Recorder()` or just cast the context.ResponseWriter to a pointer of iris.ResponseRecorder.
+
+- middleware (global, per party, per route...): `iris.UseGlobal(iris.Recorder)`/`app := iris.New(); app.UseGlobal(iris.Recorder)` or `iris.Get("/mypath", iris.Recorder, myPathHandler)`
+
+
## v5/fasthttp -> 6.0.0
View
@@ -20,7 +20,7 @@
<br/>
-<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%206.0.0%20-blue.svg?style=flat-square" alt="Releases"></a>
+<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%206.0.1%20-blue.svg?style=flat-square" alt="Releases"></a>
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
@@ -823,7 +823,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
-Current: **v6.0.0**
+Current: **v6.0.1**
Stable: **[v5/fasthttp](https://github.com/kataras/iris/tree/5.0.0)**
View
@@ -137,7 +137,7 @@ type (
// Context is resetting every time a request is coming to the server
// it is not good practice to use this object in goroutines, for these cases use the .Clone()
Context struct {
- ResponseWriter *ResponseWriter
+ ResponseWriter // *responseWriter by default, when record is on then *ResponseRecorder
Request *http.Request
values requestValues
framework *Framework
@@ -473,11 +473,6 @@ func (ctx *Context) ReadForm(formObject interface{}) error {
return errReadBody.With(formBinder.Decode(values, formObject))
}
-// ResetBody resets the body of the response
-func (ctx *Context) ResetBody() {
- ctx.ResponseWriter.ResetBody()
-}
-
/* Response */
// SetContentType sets the response writer's header key 'Content-Type' to a given value(s)
@@ -492,7 +487,7 @@ func (ctx *Context) SetHeader(k string, v string) {
// SetStatusCode sets the status code header to the response
//
-// NOTE: Iris takes cares of multiple header writing
+// same as .WriteHeader, iris takes cares of your status code seriously
func (ctx *Context) SetStatusCode(statusCode int) {
ctx.ResponseWriter.WriteHeader(statusCode)
}
@@ -519,7 +514,7 @@ func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
ctx.Log("Trying to redirect to itself. FROM: %s TO: %s", ctx.Path(), urlToRedirect)
}
}
- http.Redirect(ctx.ResponseWriter.ResponseWriter, ctx.Request, urlToRedirect, httpStatus)
+ http.Redirect(ctx.ResponseWriter, ctx.Request, urlToRedirect, httpStatus)
}
// RedirectTo does the same thing as Redirect but instead of receiving a uri or path it receives a route name
@@ -556,38 +551,6 @@ func (ctx *Context) EmitError(statusCode int) {
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
-// -----------------------------Raw write methods---------------------------------------
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
-
-// Write writes the contents to the response writer.
-//
-// Returns the number of bytes written and any write error encountered
-func (ctx *Context) Write(contents []byte) (n int, err error) {
- return ctx.ResponseWriter.Write(contents)
-}
-
-// Writef formats according to a format specifier and writes to the response.
-//
-// Returns the number of bytes written and any write error encountered
-func (ctx *Context) Writef(format string, a ...interface{}) (n int, err error) {
- return fmt.Fprintf(ctx.ResponseWriter, format, a...)
-}
-
-// WriteString writes a simple string to the response.
-//
-// Returns the number of bytes written and any write error encountered
-func (ctx *Context) WriteString(s string) (n int, err error) {
- return io.WriteString(ctx.ResponseWriter, s)
-}
-
-// SetBodyString writes a simple string to the response.
-func (ctx *Context) SetBodyString(s string) {
- ctx.ResponseWriter.SetBodyString(s)
-}
-
-// -------------------------------------------------------------------------------------
-// -------------------------------------------------------------------------------------
// -------------------------Context's gzip inline response writer ----------------------
// ---------------------Look template.go & iris.go for more options---------------------
// -------------------------------------------------------------------------------------
@@ -690,15 +653,20 @@ func (ctx *Context) RenderTemplateSource(status int, src string, binding interfa
// RenderWithStatus builds up the response from the specified template or a serialize engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engines
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) (err error) {
+
+ if _, shouldFirstStatusCode := ctx.ResponseWriter.(*responseWriter); shouldFirstStatusCode {
+ ctx.SetStatusCode(status)
+ }
+
if strings.IndexByte(name, '.') > -1 { //we have template
err = ctx.framework.templates.renderFile(ctx, name, binding, options...)
} else {
err = ctx.renderSerialized(name, binding, options...)
}
-
- if err == nil {
- ctx.SetStatusCode(status)
- }
+ // we don't care for the last one it will not be written more than one if we have the *responseWriter
+ ///TODO:
+ // if we have ResponseRecorder order doesn't matters but I think the transactions have bugs , for now let's keep it here because it 'fixes' one of them...
+ ctx.SetStatusCode(status)
return
}
@@ -1212,10 +1180,38 @@ func (ctx *Context) MaxAge() int64 {
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
-// --------------------------------Transactions-----------------------------------------
+// ---------------------------Transactions & Response Writer Recording------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
+// Record transforms the context's basic and direct responseWriter to a ResponseRecorder
+// which can be used to reset the body, reset headers, get the body,
+// get & set the status code at any time and more
+func (ctx *Context) Record() {
+ if w, ok := ctx.ResponseWriter.(*responseWriter); ok {
+ ctx.ResponseWriter = acquireResponseRecorder(w)
+ }
+}
+
+// Recorder returns the context's ResponseRecorder
+// if not recording then it starts recording and returns the new context's ResponseRecorder
+func (ctx *Context) Recorder() *ResponseRecorder {
+ ctx.Record()
+ return ctx.ResponseWriter.(*ResponseRecorder)
+}
+
+// IsRecording returns the response recorder and a true value
+// when the response writer is recording the status code, body, headers and so on,
+// else returns nil and false
+func (ctx *Context) IsRecording() (*ResponseRecorder, bool) {
+ //NOTE:
+ // two return values in order to minimize the if statement:
+ // if (Recording) then writer = Recorder()
+ // instead we do: recorder,ok = Recording()
+ rr, ok := ctx.ResponseWriter.(*ResponseRecorder)
+ return rr, ok
+}
+
// skipTransactionsContextKey set this to any value to stop executing next transactions
// it's a context-key in order to be used from anywhere, set it by calling the SkipTransactions()
const skipTransactionsContextKey = "__IRIS_TRANSACTIONS_SKIP___"
@@ -1259,6 +1255,10 @@ func (ctx *Context) BeginTransaction(pipe func(transaction *Transaction)) {
if ctx.TransactionsSkipped() {
return
}
+
+ // start recording in order to be able to control the full response writer
+ ctx.Record()
+
// get a transaction scope from the pool by passing the temp context/
t := newTransaction(ctx)
defer func() {
@@ -1273,6 +1273,7 @@ func (ctx *Context) BeginTransaction(pipe func(transaction *Transaction)) {
// write the temp contents to the original writer
t.Context.ResponseWriter.writeTo(ctx.ResponseWriter)
+
// give back to the transaction the original writer (SetBeforeFlush works this way and only this way)
// this is tricky but nessecery if we want ctx.EmitError to work inside transactions
t.Context.ResponseWriter = ctx.ResponseWriter
View
@@ -361,7 +361,6 @@ func TestContextRedirectTo(t *testing.T) {
args = append(args, s)
}
}
- //println("Redirecting to: " + routeName + " with path: " + Path(routeName, args...))
ctx.RedirectTo(routeName, args...)
})
@@ -691,7 +690,9 @@ func TestTransactions(t *testing.T) {
}
successTransaction := func(scope *iris.Transaction) {
-
+ if scope.Context.Request.RequestURI == "/failAllBecauseOfRequestScopeAndFailure" {
+ t.Fatalf("We are inside successTransaction but the previous REQUEST SCOPED TRANSACTION HAS FAILED SO THiS SHOULD NOT BE RAN AT ALL")
+ }
scope.Context.HTML(iris.StatusOK,
secondTransactionSuccessHTMLMessage)
// * if we don't have any 'throw error' logic then no need of scope.Complete()
View
@@ -886,7 +886,9 @@ func (mux *serveMux) registerError(statusCode int, handler Handler) {
mux.mu.Lock()
func(statusCode int, handler Handler) {
mux.errorHandlers[statusCode] = HandlerFunc(func(ctx *Context) {
- ctx.ResetBody()
+ if w, ok := ctx.IsRecording(); ok {
+ w.Reset()
+ }
ctx.SetStatusCode(statusCode)
handler.Serve(ctx)
})
@@ -900,14 +902,15 @@ func (mux *serveMux) fireError(statusCode int, ctx *Context) {
errHandler := mux.errorHandlers[statusCode]
if errHandler == nil {
errHandler = HandlerFunc(func(ctx *Context) {
- ctx.ResponseWriter.Reset()
+ if w, ok := ctx.IsRecording(); ok {
+ w.Reset()
+ }
ctx.SetStatusCode(statusCode)
- ctx.SetBodyString(statusText[statusCode])
+ ctx.WriteString(statusText[statusCode])
})
mux.errorHandlers[statusCode] = errHandler
}
mux.mu.Unlock()
-
errHandler.Serve(ctx)
}
@@ -1113,8 +1116,16 @@ var (
errParseTLS = errors.New("Couldn't load TLS, certFile=%q, keyFile=%q. Trace: %s")
)
+// TCPKeepAlive returns a new tcp keep alive Listener
+func TCPKeepAlive(addr string) (net.Listener, error) {
+ ln, err := net.Listen("tcp", ParseHost(addr))
+ if err != nil {
+ return nil, err
+ }
+ return TCPKeepAliveListener{ln.(*net.TCPListener)}, err
+}
+
// TCP4 returns a new tcp4 Listener
-// *tcp6 has some bugs in some operating systems, as reported by Go Community*
func TCP4(addr string) (net.Listener, error) {
return net.Listen("tcp4", ParseHost(addr))
}
@@ -1253,14 +1264,14 @@ type TCPKeepAliveListener struct {
*net.TCPListener
}
-// Accept implements the listener and sets the keep alive period which is 2minutes
+// Accept implements the listener and sets the keep alive period which is 3minutes
func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
- tc.SetKeepAlivePeriod(2 * time.Minute)
+ tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
@@ -1283,7 +1294,7 @@ func ParseHost(addr string) string {
} else if osport := os.Getenv("PORT"); osport != "" {
a = ":" + osport
} else {
- a = DefaultServerAddr
+ a = ":http"
}
}
if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
@@ -1380,6 +1391,8 @@ var ProxyHandler = func(redirectSchemeAndHost string) http.HandlerFunc {
return
}
+ // redirectTo := redirectSchemeAndHost + r.RequestURI
+
http.Redirect(w, r, redirectTo, StatusMovedPermanently)
}
}
@@ -1401,5 +1414,6 @@ func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error {
if ok := <-prx.Available; !ok {
prx.Logger.Panic("Unexpected error: proxy server cannot start, please report this as bug!!")
}
+
return func() error { return prx.Close() }
}
View
@@ -331,7 +331,7 @@ func TestMuxSimple(t *testing.T) {
iris.HandleFunc(r.Method, r.Path, func(ctx *iris.Context) {
ctx.SetStatusCode(r.Status)
if r.Params != nil && len(r.Params) > 0 {
- ctx.SetBodyString(ctx.ParamsSentence())
+ ctx.WriteString(ctx.ParamsSentence())
} else if r.URLParams != nil && len(r.URLParams) > 0 {
if len(r.URLParams) != len(ctx.URLParams()) {
t.Fatalf("Error when comparing length of url parameters %d != %d", len(r.URLParams), len(ctx.URLParams()))
@@ -344,9 +344,9 @@ func TestMuxSimple(t *testing.T) {
paramsKeyVal = paramsKeyVal[0 : len(paramsKeyVal)-1]
}
}
- ctx.SetBodyString(paramsKeyVal)
+ ctx.WriteString(paramsKeyVal)
} else {
- ctx.SetBodyString(r.Body)
+ ctx.WriteString(r.Body)
}
})
Oops, something went wrong.

0 comments on commit 058d70e

Please sign in to comment.