diff --git a/HISTORY.md b/HISTORY.md
index dc9dc268..6b123095 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -11,7 +11,8 @@ Users already notified for some breaking-changes, this section will help you
to adapt the new changes to your application, it contains an overview of the new features too.
- Shutdown with `app.Shutdown(context.Context) error`, no need for any third-parties, with `EventPolicy.Interrupted` and Go's 1.8 Gracefully Shutdown feature you're ready to go!
-- HTTP/2 Go 1.8 `context.Push(target string, opts *http.PushOptions) error` is supported
+- HTTP/2 Go 1.8 `context.Push(target string, opts *http.PushOptions) error` is supported, example can be found [here](https://github.com/kataras/iris.v6/blob/master/adaptors/websocket/_examples/webocket_secure/main.go)
+
- Router (two lines to add, new features)
- Template engines (two lines to add, same features as before, except their easier configuration)
- Basic middleware, that have been written by me, are transfared to the main repository[/middleware](https://github.com/kataras/iris/tree/master/middleware) with a lot of improvements to the `recover middleware` (see the next)
diff --git a/adaptors/websocket/_examples/websocket_secure/main.go b/adaptors/websocket/_examples/websocket_secure/main.go
new file mode 100644
index 00000000..2ceee5ec
--- /dev/null
+++ b/adaptors/websocket/_examples/websocket_secure/main.go
@@ -0,0 +1,197 @@
+package main
+
+import (
+ "fmt" // optional
+ "io/ioutil" // optional
+ "os" // optional
+ "time" // optional
+
+ "gopkg.in/kataras/iris.v6"
+ "gopkg.in/kataras/iris.v6/adaptors/httprouter"
+ "gopkg.in/kataras/iris.v6/adaptors/view"
+ "gopkg.in/kataras/iris.v6/adaptors/websocket"
+)
+
+type clientPage struct {
+ Title string
+ Host string
+}
+
+func main() {
+ app := iris.New()
+ app.Adapt(iris.DevLogger()) // enable all (error) logs
+ app.Adapt(httprouter.New()) // select the httprouter as the servemux
+ app.Adapt(view.HTML("./templates", ".html")) // select the html engine to serve templates
+
+ ws := websocket.New(websocket.Config{
+ // the path which the websocket client should listen/registed to,
+ Endpoint: "/my_endpoint",
+ // the client-side javascript static file path
+ // which will be served by Iris.
+ // default is /iris-ws.js
+ // if you change that you have to change the bottom of templates/client.html
+ // script tag:
+ ClientSourcePath: "/iris-ws.js",
+ //
+ // Set the timeouts, 0 means no timeout
+ // websocket has more configuration, go to ../../config.go for more:
+ // WriteTimeout: 0,
+ // ReadTimeout: 0,
+ // by-default all origins are accepted, you can change this behavior by setting:
+ // CheckOrigin: (r *http.Request ) bool {},
+ //
+ //
+ // IDGenerator used to create (and later on, set)
+ // an ID for each incoming websocket connections (clients).
+ // The request is an argument which you can use to generate the ID (from headers for example).
+ // If empty then the ID is generated by DefaultIDGenerator: randomString(64):
+ // IDGenerator func(ctx *iris.Context) string {},
+ })
+
+ app.Adapt(ws) // adapt the websocket server, you can adapt more than one with different Endpoint
+
+ app.StaticWeb("/js", "./static/js") // static route to serve our javascript files
+
+ app.Get("/", func(ctx *iris.Context) {
+ // send our custom javascript source file before client really asks for that
+ // using the new go v1.8's HTTP/2 Push.
+ // Note that you have to listen using ListenTLS/ListenLETSENCRYPT in order this to work.
+ if err := ctx.ResponseWriter.Push("/js/chat.js", nil); err != nil {
+ app.Log(iris.DevMode, err.Error())
+ }
+ ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
+ })
+
+ var myChatRoom = "room1"
+
+ ws.OnConnection(func(c websocket.Connection) {
+ // Context returns the (upgraded) *iris.Context of this connection
+ // avoid using it, you normally don't need it,
+ // websocket has everything you need to authenticate the user BUT if it's necessary
+ // then you use it to receive user information, for example: from headers.
+
+ // ctx := c.Context()
+
+ // join to a room (optional)
+ c.Join(myChatRoom)
+
+ c.On("chat", func(message string) {
+ if message == "leave" {
+ c.Leave(myChatRoom)
+ c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
+ c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
+ return
+ }
+ // to all except this connection ->
+ // c.To(websocket.Broadcast).Emit("chat", "Message from: "+c.ID()+"-> "+message)
+ // to all connected clients: c.To(websocket.All)
+
+ // to the client itself ->
+ //c.Emit("chat", "Message from myself: "+message)
+
+ //send the message to the whole room,
+ //all connections are inside this room will receive this message
+ c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message)
+ })
+
+ // or create a new leave event
+ // c.On("leave", func() {
+ // c.Leave(myChatRoom)
+ // })
+
+ c.OnDisconnect(func() {
+ fmt.Printf("Connection with ID: %s has been disconnected!\n", c.ID())
+ })
+ })
+
+ listenTLS(app)
+
+}
+
+// a test listenTLS for our localhost
+func listenTLS(app *iris.Framework) {
+
+ const (
+ testTLSCert = `-----BEGIN CERTIFICATE-----
+MIIDBTCCAe2gAwIBAgIJAOYzROngkH6NMA0GCSqGSIb3DQEBBQUAMBkxFzAVBgNV
+BAMMDmxvY2FsaG9zdDo4MDgwMB4XDTE3MDIxNzAzNDM1NFoXDTI3MDIxNTAzNDM1
+NFowGTEXMBUGA1UEAwwObG9jYWxob3N0OjgwODAwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCfsiVHO14FpKsi0pvBv68oApQm2MO+dCvq87sDU4E0QJhG
+KV1RCUmQVypChEqdLlUQsopcXSyKwbWoyg1/KNHYO3DHMfePb4bC1UD2HENq7Ph2
+8QJTEi/CJvUB9hqke/YCoWYdjFiI3h3Hw8q5whGO5XR3R23z69vr5XxoNlcF2R+O
+TdkzArd0CWTZS27vbgdnyi9v3Waydh/rl+QRtPUgEoCEqOOkMSMldXO6Z9GlUk9b
+FQHwIuEnlSoVFB5ot5cqebEjJnWMLLP83KOCQekJeHZOyjeTe8W0Fy1DGu5fvFNh
+xde9e/7XlFE//00vT7nBmJAUV/2CXC8U5lsjLEqdAgMBAAGjUDBOMB0GA1UdDgQW
+BBQOfENuLn/t0Z4ZY1+RPWaz7RBH+TAfBgNVHSMEGDAWgBQOfENuLn/t0Z4ZY1+R
+PWaz7RBH+TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBG7AEEuIq6
+rWCE5I2t4IXz0jN7MilqEhUWDbUajl1paYf6Ikx5QhMsFx21p6WEWYIYcnWAKZe2
+chAgnnGojuxdx0qjiaH4N4xWGHsWhaesnIF1xJepLlX3kJZQURvRxM4wlljlQPIb
+9tqzKP131K1HDqplAtp7nWQ72m3J0ZfzH0mYIUxuaS/uQIVtgKqdilwy/VE5dRZ9
+QFIb4G9TnNThXMqgTLjfNr33jVbTuv6fzKHYNbCkP3L10ydEs/ddlREmtsn9nE8Q
+XCTIYXzA2kr5kWk7d3LkUiSvu3g2S1Ol1YaIKaOQyRveseCGwR4xohLT+dPUW9dL
+3hDVLlwE3mB3
+-----END CERTIFICATE-----
+
+`
+ testTLSKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAn7IlRzteBaSrItKbwb+vKAKUJtjDvnQr6vO7A1OBNECYRild
+UQlJkFcqQoRKnS5VELKKXF0sisG1qMoNfyjR2DtwxzH3j2+GwtVA9hxDauz4dvEC
+UxIvwib1AfYapHv2AqFmHYxYiN4dx8PKucIRjuV0d0dt8+vb6+V8aDZXBdkfjk3Z
+MwK3dAlk2Utu724HZ8ovb91msnYf65fkEbT1IBKAhKjjpDEjJXVzumfRpVJPWxUB
+8CLhJ5UqFRQeaLeXKnmxIyZ1jCyz/NyjgkHpCXh2Tso3k3vFtBctQxruX7xTYcXX
+vXv+15RRP/9NL0+5wZiQFFf9glwvFOZbIyxKnQIDAQABAoIBAEzBx4ExW8PCni8i
+o5LAm2PTuXniflMwa1uGwsCahmOjGI3AnAWzPRSPkNRf2a0q8+AOsMosTphy+umi
+FFKmQBZ6m35i2earaE6FSbABbbYbKGGi/ccH2sSrDOBgdfXRTzF8eiSBrJw8hnvZ
+87rNOLtCNnSOdJ7lItODfgRo+fLo4uQenJ8VONYwtwm1ejn8qLXq8O5zF66IYUD6
+gAzqOiAWumgZL0tEmndeQ+noe4STpJZlOjiCsA12NiJaKDDeDIn5A/pXce+bYNfJ
+k4yoroyq/JXBkhyuZDvX9vYp5AA+Q68h8/KmsKkifUgSGSHun5/80lYyT/f60TLX
+PxT9GYECgYEA0s8qck7L29nBBTQ6IPF3GHGmqiRdfH+qhP/Jn4NtoW3XuVe4A15i
+REq1L8WAiOUIBnBaD8HzbeioqJJYx1pu7x9h/GCNDhdBfwhTjnBe+JjfLqvJKnc0
+HUT5wj4DVqattxKzUW8kTRBSWtVremzeffDo+EL6dnR7Bc02Ibs4WpUCgYEAwe34
+Uqhie+/EFr4HjYRUNZSNgYNAJkKHVxk4qGzG5VhvjPafnHUbo+Kk/0QW7eIB+kvR
+FDO8oKh9wTBrWZEcLJP4jDIKh4y8hZTo9B8EjxFONXVxZlOSYuGjheL8AiLzE7L9
+C1spaKMM/MyxAXDRHpG/NeEgXM7Kn6kUGwJdNekCgYAshLNiEGHcu8+XWcAs1NFh
+yB56L9PORuerzpi1pvuv65JzAaNKktQNt/krbXoHbtaTBYb/bOYLf+aeMsmsz9w9
+g1MeCQXAxAiA2zFKE1D7Ds2S/ZQt8559z+MusgnicrCcyMY1nFL+M0QxCoD4CaWy
+0v1f8EUUXuTcBMo5tV/hQQKBgDoBBW8jsiFDu7DZscSgOde00QZVzZAkAfsJLisi
+LfNXGjZdZawUUuoX1iYLpZgNK25D0wtp1hdvjf2Ej/dAMd8bexHjvcaBT7ncqjiq
+NmDcWjofIIXspTIyLwjStXGmJnJT7N/CqoYDjtTmHGND7Shpi3mAFn/r0isjFUJm
+2J5RAoGALuGXxzmSRWmkIp11F/Qr3PBFWBWkrRWaH2TRLMhrU/wO8kCsSyo4PmAZ
+ltOfD7InpDiCu43hcDPQ/29FUbDnmAhvMnmIQuHXGgPF/LhqEhbKPA/o/eZdQVCK
+QG+tmveBBIYMed5YbWstZu/95lIHF+u8Hl+Z6xgveozfE5yqiUA=
+-----END RSA PRIVATE KEY-----
+
+ `
+ )
+
+ // create the key and cert files on the fly, and delete them when this test finished
+ certFile, ferr := ioutil.TempFile("", "cert")
+
+ if ferr != nil {
+ panic(ferr)
+ }
+
+ keyFile, ferr := ioutil.TempFile("", "key")
+ if ferr != nil {
+ panic(ferr)
+ }
+
+ certFile.WriteString(testTLSCert)
+ keyFile.WriteString(testTLSKey)
+
+ // add an event when control+C pressed, to remove the temp cert and key files.
+ app.Adapt(iris.EventPolicy{
+ Interrupted: func(*iris.Framework) {
+ certFile.Close()
+ time.Sleep(50 * time.Millisecond)
+ os.Remove(certFile.Name())
+
+ keyFile.Close()
+ time.Sleep(50 * time.Millisecond)
+ os.Remove(keyFile.Name())
+ },
+ })
+
+ // https://localhost
+ app.ListenTLS("localhost:443", certFile.Name(), keyFile.Name())
+}
diff --git a/adaptors/websocket/_examples/websocket_secure/static/js/chat.js b/adaptors/websocket/_examples/websocket_secure/static/js/chat.js
new file mode 100644
index 00000000..1bef06bd
--- /dev/null
+++ b/adaptors/websocket/_examples/websocket_secure/static/js/chat.js
@@ -0,0 +1,38 @@
+var messageTxt;
+var messages;
+
+$(function () {
+
+ messageTxt = $("#messageTxt");
+ messages = $("#messages");
+
+ /* secure wss because we ListenTLS */
+ w = new Ws("wss://" + HOST + "/my_endpoint");
+ w.OnConnect(function () {
+ console.log("Websocket connection established");
+ });
+
+ w.OnDisconnect(function () {
+ appendMessage($("
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iris.go b/iris.go
index 7f35e202..96facc91 100644
--- a/iris.go
+++ b/iris.go
@@ -100,8 +100,6 @@ type Framework struct {
// These are setted by user's call to .Adapt
policies Policies
- ln net.Listener // setted on Listten/Serve funcions, available after 'Boot'
-
// TLSNextProto optionally specifies a function to take over
// ownership of the provided TLS connection when an NPN/ALPN
// protocol upgrade has occurred. The map key is the protocol
@@ -210,19 +208,15 @@ func New(setters ...OptionSetter) *Framework {
s.Adapt(EventPolicy{Boot: func(s *Framework) {
// set the host and scheme
if s.Config.VHost == "" { // if not setted by Listen functions
- if s.ln != nil { // but user called .Serve
- // then take the listener's addr
- s.Config.VHost = s.ln.Addr().String()
- } else {
- // if no .Serve or .Listen called, then the user should set the VHost manually,
- // however set it to a default value here for any case
- s.Config.VHost = DefaultServerAddr
- }
+ s.Config.VHost = DefaultServerAddr
}
- // if user didn't specified a scheme then get it from the VHost, which is already setted at before statements
+ // if user didn't specified a scheme then get it from the VHost,
+ // which is already setted at before statements
if s.Config.VScheme == "" {
+ // if :443 or :https then returns https:// otherwise http://
s.Config.VScheme = ParseScheme(s.Config.VHost)
}
+
}})
{
@@ -436,28 +430,21 @@ func (s *Framework) Boot() (firstTime bool) {
return
}
-// Serve serves incoming connections from the given listener.
-//
-// Serve blocks until the given listener returns permanent error.
-func (s *Framework) Serve(ln net.Listener) error {
- if s.ln != nil {
- return errors.New("server is already started and listening")
- }
-
- s.ln = ln
+func (s *Framework) setupServe() (srv *http.Server, deferFn func()) {
s.closedManually = false
+
s.Boot()
- // post any panics to the user defined logger.
- defer func() {
+ deferFn = func() {
+ // post any panics to the user defined logger.
if rerr := recover(); rerr != nil {
if err, ok := rerr.(error); ok {
s.handlePanic(err)
}
}
- }()
+ }
- srv := &http.Server{
+ srv = &http.Server{
ReadTimeout: s.Config.ReadTimeout,
WriteTimeout: s.Config.WriteTimeout,
MaxHeaderBytes: s.Config.MaxHeaderBytes,
@@ -467,21 +454,38 @@ func (s *Framework) Serve(ln net.Listener) error {
ErrorLog: s.policies.LoggerPolicy.ToLogger(log.LstdFlags),
Handler: s.Router,
}
+
// Set the grace shutdown, it's just a func no need to make things complicated
// all are managed by net/http now.
s.Shutdown = func(ctx context.Context) error {
// order matters, look s.handlePanic
s.closedManually = true
err := srv.Shutdown(ctx)
- s.ln = nil
return err
}
+ return
+}
+
+// Serve serves incoming connections from the given listener.
+//
+// Serve blocks until the given listener returns permanent error.
+func (s *Framework) Serve(ln net.Listener) error {
+ if ln == nil {
+ return errors.New("nil net.Listener on Serve")
+ }
+
+ // if user called .Serve and doesn't uses any nginx-like balancers.
+ if s.Config.VHost == "" {
+ s.Config.VHost = ParseHost(ln.Addr().String())
+ } // Scheme will be checked from Boot state.
+
+ srv, fn := s.setupServe()
+ defer fn()
+
// print the banner and wait for system channel interrupt
go s.postServe()
- // finally return the error or block here, remember,
- // until go1.8 these are our best options.
- return srv.Serve(s.ln)
+ return srv.Serve(ln)
}
func (s *Framework) postServe() {
@@ -507,14 +511,15 @@ func (s *Framework) postServe() {
// If you need to manually monitor any error please use `.Serve` instead.
func (s *Framework) Listen(addr string) {
addr = ParseHost(addr)
- if s.Config.VHost == "" {
- s.Config.VHost = addr
- // this will be set as the front-end listening addr
- }
- // only here, other Listen functions should throw an error if port is missing.
- // User should know how to fix them on ListenUNIX/ListenTLS/ListenLETSENCRYPT/Serve,
- // they are used by more 'advanced' devs, mostly.
+ // if .Listen called normally and VHost is not setted,
+ // so it's Host is the Real listening addr and user-given
+ if s.Config.VHost == "" {
+ s.Config.VHost = addr // as it is
+ // this will be set as the front-end listening addr
+ } // VScheme will be checked on Boot.
+
+ // this check, only here, other Listen functions should throw an error if port is missing.
if portIdx := strings.IndexByte(addr, ':'); portIdx < 0 {
// missing port part, add it
addr = addr + ":80"
@@ -538,16 +543,27 @@ func (s *Framework) Listen(addr string) {
// If you need to manually monitor any error please use `.Serve` instead.
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
addr = ParseHost(addr)
- if s.Config.VHost == "" {
- s.Config.VHost = addr
- // this will be set as the front-end listening addr
+
+ {
+ // set it before Boot, be-careful VHost and VScheme are used by nginx users too
+ // we don't want to alt them.
+ if s.Config.VHost == "" {
+ s.Config.VHost = addr
+ // this will be set as the front-end listening addr
+ }
+ if s.Config.VScheme == "" {
+ s.Config.VScheme = SchemeHTTPS
+ }
}
- ln, err := TLS(addr, certFile, keyFile)
- if err != nil {
- s.handlePanic(err)
- }
- s.Must(s.Serve(ln))
+ srv, fn := s.setupServe()
+ // We are doing the same parts as .Serve does but instead we run srv.ListenAndServeTLS
+ // because of un-exported net/http.server.go:setupHTTP2_ListenAndServeTLS function which
+ // broke our previous flow but no problem :)
+ defer fn()
+ // print the banner and wait for system channel interrupt
+ go s.postServe()
+ s.Must(srv.ListenAndServeTLS(certFile, keyFile))
}
// ListenLETSENCRYPT starts a server listening at the specific nat address
@@ -557,16 +573,25 @@ func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
// if you skip the second parameter then the cache file is "./letsencrypt.cache"
// if you want to disable cache then simple pass as second argument an empty empty string ""
//
-// example: https://github.com/iris-contrib/examples/blob/master/letsencrypt/main.go
+// Note: HTTP/2 Push is not working with LETSENCRYPT, you have to use ListenTLS to enable HTTP/2
+// Because net/http's author didn't exported the functions to tell the server that is using HTTP/2...
//
-// supports localhost domains for testing,
-// NOTE: if you are ready for production then use `$app.Serve(iris.LETSENCRYPTPROD("mydomain.com"))` instead
+// example: https://github.com/iris-contrib/examples/blob/master/letsencrypt/main.go
func (s *Framework) ListenLETSENCRYPT(addr string, cacheFileOptional ...string) {
addr = ParseHost(addr)
- if s.Config.VHost == "" {
- s.Config.VHost = addr
- // this will be set as the front-end listening addr
+
+ {
+ // set it before Boot, be-careful VHost and VScheme are used by nginx users too
+ // we don't want to alt them.
+ if s.Config.VHost == "" {
+ s.Config.VHost = addr
+ // this will be set as the front-end listening addr
+ }
+ if s.Config.VScheme == "" {
+ s.Config.VScheme = SchemeHTTPS
+ }
}
+
ln, err := LETSENCRYPT(addr, cacheFileOptional...)
if err != nil {
s.handlePanic(err)
diff --git a/router.go b/router.go
index fb9e33ce..4360bd68 100644
--- a/router.go
+++ b/router.go
@@ -607,9 +607,9 @@ func (router *Router) StaticHandler(reqPath string, systemPath string, showList
// second parameter: the system directory
// third OPTIONAL parameter: the exception routes
// (= give priority to these routes instead of the static handler)
-// for more options look iris.StaticHandler.
+// for more options look router.StaticHandler.
//
-// iris.StaticWeb("/static", "./static")
+// router.StaticWeb("/static", "./static")
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
@@ -618,8 +618,22 @@ func (router *Router) StaticHandler(reqPath string, systemPath string, showList
// StaticWeb calls the StaticHandler(reqPath, systemPath, listingDirectories: false, gzip: false ).
func (router *Router) StaticWeb(reqPath string, systemPath string, exceptRoutes ...RouteInfo) RouteInfo {
h := router.StaticHandler(reqPath, systemPath, false, false, exceptRoutes...)
- routePath := validateWildcard(reqPath, "file")
- return router.registerResourceRoute(routePath, h)
+ paramName := "file"
+ routePath := validateWildcard(reqPath, paramName)
+ handler := func(ctx *Context) {
+ h(ctx)
+ if fname := ctx.Param(paramName); fname != "" {
+ cType := fs.TypeByExtension(fname)
+ if cType != contentBinary && !strings.Contains(cType, "charset") {
+ cType += "; charset=" + ctx.framework.Config.Charset
+ }
+
+ ctx.SetContentType(cType)
+ }
+
+ }
+
+ return router.registerResourceRoute(routePath, handler)
}
// Layout oerrides the parent template layout with a more specific layout for this Party