From d7ec0d44165086fdb95104559e1aac49263ff6a4 Mon Sep 17 00:00:00 2001 From: hiveminded Date: Sat, 2 Sep 2017 14:32:14 +0300 Subject: [PATCH] add content type and response text to the Controller :100: Former-commit-id: 99cde0a445027b10839155501a7918732a783af3 --- _examples/hello-world/main.go | 6 +-- _examples/mvc/hello-world/main.go | 61 +++++++++------------- context/context.go | 17 +++++-- core/router/router_wildcard_root_test.go | 2 +- doc.go | 30 ++++++----- mvc/controller.go | 64 ++++++++++++++++++------ 6 files changed, 107 insertions(+), 73 deletions(-) diff --git a/_examples/hello-world/main.go b/_examples/hello-world/main.go index 5a1d9132..78abd5f8 100644 --- a/_examples/hello-world/main.go +++ b/_examples/hello-world/main.go @@ -16,9 +16,9 @@ func main() { app.Use(logger.New()) // Method: GET - // Resource: http://localhost:8080/ + // Resource: http://localhost:8080 app.Handle("GET", "/", func(ctx iris.Context) { - ctx.HTML("Welcome!") + ctx.HTML("

Welcome

") }) // same as app.Handle("GET", "/ping", [...]) @@ -31,7 +31,7 @@ func main() { // Method: GET // Resource: http://localhost:8080/hello app.Get("/hello", func(ctx iris.Context) { - ctx.JSON(iris.Map{"message": "Hello iris web framework."}) + ctx.JSON(iris.Map{"message": "Hello Iris!"}) }) // http://localhost:8080 diff --git a/_examples/mvc/hello-world/main.go b/_examples/mvc/hello-world/main.go index 86ca97eb..fbd6a259 100644 --- a/_examples/mvc/hello-world/main.go +++ b/_examples/mvc/hello-world/main.go @@ -35,9 +35,7 @@ func main() { app.Use(recover.New()) app.Use(logger.New()) - app.Controller("/", new(IndexController)) - app.Controller("/ping", new(PingController)) - app.Controller("/hello", new(HelloController)) + app.Controller("/", new(ExampleController)) // http://localhost:8080 // http://localhost:8080/ping @@ -45,63 +43,50 @@ func main() { app.Run(iris.Addr(":8080")) } -// IndexController serves the "/". -type IndexController struct { +// ExampleController serves the "/", "/ping" and "/hello". +type ExampleController struct { // if you build with go1.8 you have to use the mvc package, `mvc.Controller` instead. iris.Controller } // Get serves // Method: GET -// Resource: http://localhost:8080/ -func (c *IndexController) Get() { - c.Ctx.HTML("Welcome!") +// Resource: http://localhost:8080 +func (c *ExampleController) Get() { + c.ContentType = "text/html" + c.Text = "

Welcome!

" } -// PingController serves the "/ping". -type PingController struct { - iris.Controller -} - -// Get serves +// GetPing serves // Method: GET // Resource: http://localhost:8080/ping -func (c *PingController) Get() { - c.Ctx.WriteString("pong") +func (c *ExampleController) GetPing() { + c.Text = "pong" } -// HelloController serves the "/hello". -type HelloController struct { - iris.Controller -} - -type myJSONData struct { - Message string `json:"message"` -} - -// Get serves +// GetHello serves // Method: GET // Resource: http://localhost:8080/hello -func (c *HelloController) Get() { - c.Ctx.JSON(myJSONData{"Hello iris web framework."}) +func (c *ExampleController) GetHello() { + c.Ctx.JSON(iris.Map{"message": "Hello Iris!"}) } /* Can use more than one, the factory will make sure that the correct http methods are being registered for each route for this controller, uncomment these if you want: -func (c *HelloController) Post() {} -func (c *HelloController) Put() {} -func (c *HelloController) Delete() {} -func (c *HelloController) Connect() {} -func (c *HelloController) Head() {} -func (c *HelloController) Patch() {} -func (c *HelloController) Options() {} -func (c *HelloController) Trace() {} +func (c *ExampleController) Post() {} +func (c *ExampleController) Put() {} +func (c *ExampleController) Delete() {} +func (c *ExampleController) Connect() {} +func (c *ExampleController) Head() {} +func (c *ExampleController) Patch() {} +func (c *ExampleController) Options() {} +func (c *ExampleController) Trace() {} */ /* -func (c *HelloController) All() {} +func (c *ExampleController) All() {} // OR -func (c *HelloController) Any() {} +func (c *ExampleController) Any() {} */ diff --git a/context/context.go b/context/context.go index 5e4879b9..2860a62f 100644 --- a/context/context.go +++ b/context/context.go @@ -1366,12 +1366,21 @@ func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, func (ctx *context) Redirect(urlToRedirect string, statusHeader ...int) { ctx.StopExecution() - httpStatus := http.StatusFound // a 'temporary-redirect-like' which works better than for our purpose - if len(statusHeader) > 0 && statusHeader[0] > 0 { - httpStatus = statusHeader[0] + // get the previous status code given by the end-developer. + status := ctx.GetStatusCode() + if len(statusHeader) > 0 { + // check if status code is passed via receivers. + if s := statusHeader[0]; s > 0 { + status = s + } + } + if status == 0 { + // if status remains zero then default it. + // a 'temporary-redirect-like' which works better than for our purpose + status = http.StatusFound } - http.Redirect(ctx.writer, ctx.request, urlToRedirect, httpStatus) + http.Redirect(ctx.writer, ctx.request, urlToRedirect, status) } // +------------------------------------------------------------+ diff --git a/core/router/router_wildcard_root_test.go b/core/router/router_wildcard_root_test.go index da9dbd33..110192eb 100644 --- a/core/router/router_wildcard_root_test.go +++ b/core/router/router_wildcard_root_test.go @@ -169,7 +169,7 @@ func testTheRoutes(t *testing.T, tests []testRoute, debug bool) { if method == "" { method = tt.method } - ex := e.Request(tt.method, req.path) + ex := e.Request(method, req.path) if req.subdomain != "" { ex.WithURL("http://" + req.subdomain + ".localhost:8080") } diff --git a/doc.go b/doc.go index d86fa52b..5f210591 100644 --- a/doc.go +++ b/doc.go @@ -163,10 +163,10 @@ Example code: Listening and gracefully shutdown -You can listen to a server using any type of net.Listener or http.Server instance. +You can start the server(s) listening to any type of `net.Listener` or even `http.Server` instance. The method for initialization of the server should be passed at the end, via `Run` function. -Below you'll read some usage examples: +Below you'll see some useful examples: // Listening on tcp with network address 0.0.0.0:8080 @@ -190,13 +190,22 @@ Below you'll read some usage examples: // Automatic TLS - app.Run(iris.AutoTLS("localhost:443")) + app.Run(iris.AutoTLS(":443", "example.com", "admin@example.com")) // UNIX socket - l, err := netutil.UNIX("/tmpl/srv.sock", 0666) + if errOs := os.Remove(socketFile); errOs != nil && !os.IsNotExist(errOs) { + app.Logger().Fatal(errOs) + } + + l, err := net.Listen("unix", socketFile) + if err != nil { - panic(err) + app.Logger().Fatal(err) + } + + if err = os.Chmod(socketFile, mode); err != nil { + app.Logger().Fatal(err) } app.Run(iris.Listener(l)) @@ -454,7 +463,7 @@ Example code: app.Any("/", handler) func handler(ctx iris.Context){ - ctx.Writef("Hello from method: %s and path: %s", ctx.Method(), ctx.Path()) + ctx.Writef("Hello from method: %s and path: %s", ctx.Method(), ctx.Path()) } @@ -471,15 +480,12 @@ A group can have a nested group too. Example code: - users:= app.Party("/users", myAuthHandler) + users := app.Party("/users", myAuthMiddlewareHandler) // http://myhost.com/users/42/profile - users.Get("/{userid:int}/profile", userProfileHandler) + users.Get("/{id:int}/profile", userProfileHandler) // http://myhost.com/users/messages/1 - users.Get("/inbox/{messageid:int}", userMessageHandler) - - app.Run(iris.Addr("myhost.com:80")) - + users.Get("/inbox/{id:int}", userMessageHandler) Custom HTTP Errors diff --git a/mvc/controller.go b/mvc/controller.go index c8763070..1fe53670 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -63,17 +63,18 @@ import ( // Look `core/router/APIBuilder#Controller` method too. type Controller struct { // Name contains the current controller's full name. + // + // doesn't change on different paths. Name string - // currentRoute is the current request context's route. - currentRoute context.RouteReadOnly - // contains the `Name` as different words, all lowercase, // without the "Controller" suffix if exists. // we need this as field because the activator // we will not try to parse these if not needed // it's up to the end-developer to call `RelPath()` or `RelTmpl()` // which will result to fill them. + // + // doesn't change on different paths. nameAsWords []string // relPath the "as assume" relative request path. @@ -81,10 +82,12 @@ type Controller struct { // If UserController and request path is "/user/messages" then it's "/messages" // if UserPostController and request path is "/user/post" then it's "/" // if UserProfile and request path is "/user/profile/likes" then it's "/likes" + // + // doesn't change on different paths. relPath string // request path and its parameters, read-write. - // Path is the current request path. + // Path is the current request path, if changed then it redirects. Path string // Params are the request path's parameters, i.e // for route like "/user/{id}" and request to "/user/42" @@ -101,13 +104,19 @@ type Controller struct { // If UserController then it's "user/" // if UserPostController then it's "user/post/" // if UserProfile then it's "user/profile/". + // + // doesn't change on different paths. relTmpl string + // view read and write, // can be already set-ed by previous handlers as well. Layout string Tmpl string Data map[string]interface{} + ContentType string + Text string // or Text + // give access to the request context itself. Ctx context.Context } @@ -127,10 +136,7 @@ func (c *Controller) getNameWords() []string { // Route returns the current request controller's context read-only access route. func (c *Controller) Route() context.RouteReadOnly { - if c.currentRoute == nil { - c.currentRoute = c.Ctx.GetCurrentRoute() - } - return c.currentRoute + return c.Ctx.GetCurrentRoute() } const slashStr = "/" @@ -208,6 +214,13 @@ func (c *Controller) RelTmpl() string { return c.relTmpl } +// Write writes to the client via the context's ResponseWriter. +// Controller completes the `io.Writer` interface for the shake of ease. +func (c *Controller) Write(contents []byte) (int, error) { + c.tryWriteHeaders() + return c.Ctx.ResponseWriter().Write(contents) +} + // BeginRequest starts the main controller // it initialize the Ctx and other fields. // @@ -221,12 +234,26 @@ func (c *Controller) BeginRequest(ctx context.Context) { c.Status = ctx.GetStatusCode() // share values c.Values = ctx.Values() - // view + // view data for templates, remember + // each controller is a new instance, so + // checking for nil and then init those type of fields + // have no meaning. c.Data = make(map[string]interface{}, 0) + // context itself c.Ctx = ctx } +func (c *Controller) tryWriteHeaders() { + if status := c.Status; status > 0 && status != c.Ctx.GetStatusCode() { + c.Ctx.StatusCode(status) + } + + if contentType := c.ContentType; contentType != "" { + c.Ctx.ContentType(contentType) + } +} + // EndRequest is the final method which will be executed // before response sent. // @@ -236,25 +263,32 @@ func (c *Controller) BeginRequest(ctx context.Context) { // It's called internally. // End-Developer can ovveride it but still should be called at the end. func (c *Controller) EndRequest(ctx context.Context) { - if path := c.Path; path != "" && path != ctx.Path() { - // then redirect - ctx.Redirect(path) + if ctx.ResponseWriter().Written() > 0 { return } - if status := c.Status; status > 0 && status != ctx.GetStatusCode() { - ctx.StatusCode(status) + if path := c.Path; path != "" && path != ctx.Path() { + // then redirect and exit. + ctx.Redirect(path, c.Status) + return + } + + c.tryWriteHeaders() + if response := c.Text; response != "" { + ctx.WriteString(response) + return // exit here } if view := c.Tmpl; view != "" { if layout := c.Layout; layout != "" { ctx.ViewLayout(layout) } - if data := c.Data; data != nil { + if data := c.Data; len(data) > 0 { for k, v := range data { ctx.ViewData(k, v) } } + ctx.View(view) } }