mirror of
				https://github.com/labstack/echo.git
				synced 2025-10-30 23:57:38 +02:00 
			
		
		
		
	
							
								
								
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							| @@ -14,8 +14,7 @@ | ||||
| *.json  text eol=lf | ||||
| LICENSE text eol=lf | ||||
|  | ||||
| # Exclude `website` and `examples` from Github's language statistics | ||||
| # Exclude `website` and `recipes` from Github's language statistics | ||||
| # https://github.com/github/linguist#using-gitattributes | ||||
| examples/* linguist-documentation | ||||
| recipes/* linguist-documentation | ||||
| website/* linguist-documentation | ||||
|   | ||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # [Echo](http://echo.labstack.com) [](http://godoc.org/github.com/labstack/echo) [](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) [](https://travis-ci.org/labstack/echo) [](https://coveralls.io/r/labstack/echo) [](https://gitter.im/labstack/echo) | ||||
| # [Echo](http://labstack.com/echo) [](http://godoc.org/github.com/labstack/echo) [](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) [](https://travis-ci.org/labstack/echo) [](https://coveralls.io/r/labstack/echo) [](https://gitter.im/labstack/echo) | ||||
|  | ||||
| A fast and unfancy micro web framework for Go. | ||||
|  | ||||
| @@ -87,17 +87,9 @@ BenchmarkZeus_GithubAll              2000            748827 ns/op          30068 | ||||
| $ go get github.com/labstack/echo | ||||
| ``` | ||||
|  | ||||
| ## [Recipes](https://github.com/labstack/echo/tree/master/recipes) | ||||
| ## [Recipes](http://labstack.com/echo/recipes/hello-world) | ||||
|  | ||||
| - [File Upload](http://echo.labstack.com/recipes/file-upload) | ||||
| - [Streaming File Upload](http://echo.labstack.com/recipes/streaming-file-upload) | ||||
| - [Streaming Response](http://echo.labstack.com/recipes/streaming-response) | ||||
| - [WebSocket](http://echo.labstack.com/recipes/websocket) | ||||
| - [Subdomains](http://echo.labstack.com/recipes/subdomains) | ||||
| - [JWT Authentication](http://echo.labstack.com/recipes/jwt-authentication) | ||||
| - [Graceful Shutdown](http://echo.labstack.com/recipes/graceful-shutdown) | ||||
|  | ||||
| ##[Guide](http://echo.labstack.com/guide) | ||||
| ## [Guide](http://labstack.com/echo/guide) | ||||
|  | ||||
| ## Echo System | ||||
|  | ||||
|   | ||||
							
								
								
									
										12
									
								
								echo_test.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								echo_test.go
									
									
									
									
									
								
							| @@ -43,7 +43,7 @@ func TestEcho(t *testing.T) { | ||||
|  | ||||
| func TestEchoIndex(t *testing.T) { | ||||
| 	e := New() | ||||
| 	e.Index("examples/website/public/index.html") | ||||
| 	e.Index("recipes/website/public/index.html") | ||||
| 	c, b := request(GET, "/", e) | ||||
| 	assert.Equal(t, http.StatusOK, c) | ||||
| 	assert.NotEmpty(t, b) | ||||
| @@ -51,7 +51,7 @@ func TestEchoIndex(t *testing.T) { | ||||
|  | ||||
| func TestEchoFavicon(t *testing.T) { | ||||
| 	e := New() | ||||
| 	e.Favicon("examples/website/public/favicon.ico") | ||||
| 	e.Favicon("recipes/website/public/favicon.ico") | ||||
| 	c, b := request(GET, "/favicon.ico", e) | ||||
| 	assert.Equal(t, http.StatusOK, c) | ||||
| 	assert.NotEmpty(t, b) | ||||
| @@ -61,23 +61,23 @@ func TestEchoStatic(t *testing.T) { | ||||
| 	e := New() | ||||
|  | ||||
| 	// OK | ||||
| 	e.Static("/scripts", "examples/website/public/scripts") | ||||
| 	e.Static("/scripts", "recipes/website/public/scripts") | ||||
| 	c, b := request(GET, "/scripts/main.js", e) | ||||
| 	assert.Equal(t, http.StatusOK, c) | ||||
| 	assert.NotEmpty(t, b) | ||||
|  | ||||
| 	// No file | ||||
| 	e.Static("/scripts", "examples/website/public/scripts") | ||||
| 	e.Static("/scripts", "recipes/website/public/scripts") | ||||
| 	c, _ = request(GET, "/scripts/index.js", e) | ||||
| 	assert.Equal(t, http.StatusNotFound, c) | ||||
|  | ||||
| 	// Directory | ||||
| 	e.Static("/scripts", "examples/website/public/scripts") | ||||
| 	e.Static("/scripts", "recipes/website/public/scripts") | ||||
| 	c, _ = request(GET, "/scripts", e) | ||||
| 	assert.Equal(t, http.StatusForbidden, c) | ||||
|  | ||||
| 	// Directory with index.html | ||||
| 	e.Static("/", "examples/website/public") | ||||
| 	e.Static("/", "recipes/website/public") | ||||
| 	c, r := request(GET, "/", e) | ||||
| 	assert.Equal(t, http.StatusOK, c) | ||||
| 	assert.Equal(t, true, strings.HasPrefix(r, "<!doctype html>")) | ||||
|   | ||||
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| @@ -5,16 +5,16 @@ | ||||
|     "canonifyurls": true, | ||||
|  | ||||
|     "menu": { | ||||
|         "main": [{ | ||||
|             "Name": "Guide", | ||||
|             "Pre": "<i class='fa fa-book'></i>", | ||||
|             "Weight": 10, | ||||
|             "Identifier": "guide", | ||||
|             "URL": "guide" | ||||
|         "side": [{ | ||||
|             "name": "Guide", | ||||
|             "pre": "<i class='fa fa-book'></i>", | ||||
|             "weight": 1, | ||||
|             "identifier": "guide", | ||||
|             "url": "guide" | ||||
|         }, { | ||||
|             "Name": "Recipes", | ||||
|             "Pre": "<i class='fa fa-code'></i>", | ||||
|             "Weight": 20, | ||||
|             "Weight": 2, | ||||
|             "Identifier": "recipes", | ||||
|             "URL": "recipes" | ||||
|         }] | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: Customization | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: guide | ||||
|     weight: 2 | ||||
| --- | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: Error Handling | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: guide | ||||
|     weight: 7 | ||||
| --- | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: Installation | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: guide | ||||
|     weight: 1 | ||||
| --- | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: Middleware | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: guide | ||||
|     weight: 4 | ||||
| --- | ||||
| @@ -64,4 +64,4 @@ control to the centralized | ||||
| e.Use(mw.Recover()) | ||||
| ``` | ||||
|  | ||||
| [Examples](https://github.com/labstack/echo/tree/master/examples/middleware) | ||||
| ### [Recipes](https://github.com/labstack/echo/tree/master/recipes/middleware) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: Request | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: guide | ||||
|     weight: 5 | ||||
| --- | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: Response | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: guide | ||||
|     weight: 6 | ||||
| --- | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: Routing | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: guide | ||||
|     weight: 3 | ||||
| --- | ||||
|   | ||||
| @@ -105,7 +105,7 @@ Hello, World! on the page. | ||||
|  | ||||
| ### Next? | ||||
| - Browse [recipes](https://github.com/labstack/echo/tree/master/recipes) | ||||
| - Head over to [Guide]({{< relref "guide/installation.md" >}}) | ||||
| - Head over to [guide]({{< relref "guide/installation.md" >}}) | ||||
|  | ||||
| ## Contribute | ||||
|  | ||||
|   | ||||
							
								
								
									
										19
									
								
								website/content/recipes/crud.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								website/content/recipes/crud.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| --- | ||||
| title: CRUD | ||||
| menu: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 2 | ||||
| --- | ||||
|  | ||||
| ### Server | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| {{< embed "crud/server.go" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/crud) | ||||
| @@ -1,8 +1,9 @@ | ||||
| --- | ||||
| title: File Upload | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 7 | ||||
| --- | ||||
|  | ||||
| - Multipart/form-data file upload | ||||
| @@ -15,89 +16,16 @@ us an option to specify the maximum memory used while parsing the request body. | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"os" | ||||
|  | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/labstack/echo" | ||||
| 	mw "github.com/labstack/echo/middleware" | ||||
| ) | ||||
|  | ||||
| func upload(c *echo.Context) error { | ||||
| 	req := c.Request() | ||||
| 	req.ParseMultipartForm(16 << 20) // Max memory 16 MiB | ||||
|  | ||||
| 	// Read form fields | ||||
| 	name := c.Form("name") | ||||
| 	email := c.Form("email") | ||||
|  | ||||
| 	// Read files | ||||
| 	files := req.MultipartForm.File["files"] | ||||
| 	for _, f := range files { | ||||
| 		// Source file | ||||
| 		src, err := f.Open() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer src.Close() | ||||
|  | ||||
| 		// Destination file | ||||
| 		dst, err := os.Create(f.Filename) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer dst.Close() | ||||
|  | ||||
| 		if _, err = io.Copy(dst, src); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return c.String(http.StatusOK, "Thank You! %s <%s>, %d files uploaded successfully.", | ||||
| 		name, email, len(files)) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	e := echo.New() | ||||
| 	e.Use(mw.Logger()) | ||||
| 	e.Use(mw.Recover()) | ||||
|  | ||||
| 	e.Static("/", "public") | ||||
| 	e.Post("/upload", upload) | ||||
|  | ||||
| 	e.Run(":1323") | ||||
| } | ||||
| ``` | ||||
| {{< embed "file-upload/server.go" >}} | ||||
|  | ||||
| ### Client | ||||
|  | ||||
| `index.html` | ||||
|  | ||||
| ```html | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <title>File Upload</title> | ||||
| </head> | ||||
| <body> | ||||
|     <h1>Upload Files</h1> | ||||
|     <form action="/upload" method="post" enctype="multipart/form-data"> | ||||
|         Name: <input type="text" name="name"><br> | ||||
|         Email: <input type="email" name="email"><br> | ||||
|         Files: <input type="file" name="files" multiple><br><br> | ||||
|         <input type="submit" value="Submit"> | ||||
|     </form> | ||||
| </body> | ||||
| </html> | ||||
| ``` | ||||
| {{< embed "file-upload/public/index.html" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](http://github.com/vishr) | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/file-upload) | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| --- | ||||
| title: Google App Engine | ||||
| draft: false | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 12 | ||||
| --- | ||||
|  | ||||
| Google App Engine (GAE) provides a range of hosting options from pure PaaS (App Engine Classic) | ||||
| @@ -30,12 +30,8 @@ and because it is a `var` this will happen _before_ any `init()` functions run - | ||||
| use to connect our handlers later. | ||||
|  | ||||
| `app.go` | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| // referecnce our echo instance and create it early | ||||
| var e = createMux() | ||||
| ```   | ||||
| {{< embed "google-app-engine/app.go" >}} | ||||
|  | ||||
| A separate source file contains the function to create the Echo instance and add the static | ||||
| file handlers and middleware. Note the build tag on the first line which says to use this when _not_ | ||||
| @@ -43,33 +39,8 @@ bulding with appengine or appenginevm tags (which thoese platforms automatically | ||||
| have the `main()` function to start serving our app as normal. This should all be very familiar. | ||||
|  | ||||
| `app-standalone.go` | ||||
| ```go | ||||
| // +build !appengine,!appenginevm | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
|     "github.com/labstack/echo" | ||||
|     "github.com/labstack/echo/middleware" | ||||
| ) | ||||
|  | ||||
| func createMux() *echo.Echo { | ||||
|     e := echo.New() | ||||
|  | ||||
|     e.Use(middleware.Recover()) | ||||
|     e.Use(middleware.Logger()) | ||||
|     e.Use(middleware.Gzip()) | ||||
|  | ||||
|     e.Index("public/index.html") | ||||
|     e.Static("/public", "public") | ||||
|  | ||||
|     return e | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|     e.Run(":8080") | ||||
| } | ||||
| ``` | ||||
| {{< embed "google-app-engine/app-standalone.go" >}} | ||||
|  | ||||
| The handler-wireup that would normally also be a part of this Echo setup moves to separate files which | ||||
| take advantage of the ability to have multiple `init()` functions which run _after_ the `e` Echo var is | ||||
| @@ -79,26 +50,8 @@ per REST endpoint, often with a higher-level `api` group created that they attac | ||||
| Echo instance directly (so things like CORS middleware can be added at this higher common-level). | ||||
|  | ||||
| `some-endpoint.go` | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import "github.com/labstack/echo" | ||||
|  | ||||
| func init() { | ||||
| 	// our endpoint registers itself with the echo instance | ||||
| 	// and could add it's own middleware if it needed | ||||
| 	g := e.Group("/some-endpoint") | ||||
| 	g.Get("", listHandler) | ||||
| 	g.Get("/:id", getHandler) | ||||
| 	g.Patch("/:id", updateHandler) | ||||
| 	g.Post("/:id", addHandler) | ||||
| 	g.Put("/:id", replaceHandler) | ||||
| 	g.Delete("/:id", deleteHandler) | ||||
| } | ||||
|  | ||||
| func listHandler(c *echo.Context) error { | ||||
| 	// actual handler implementations ... | ||||
| ``` | ||||
| {{< embed "google-app-engine/some-endpoint.go" >}} | ||||
|  | ||||
| If we run our app it should execute as it did before when everything was in one file although we have | ||||
| at least gained the ability to organize our handlers a little more cleanly. | ||||
| @@ -125,45 +78,9 @@ The yaml file also contains other options to control instance size and auto-scal | ||||
| deployment freedom you would likely have separate `app-classic.yaml` and `app-vm.yaml` files and | ||||
| this can help when making the transition from AppEngine Classic to Managed VMs. | ||||
|  | ||||
| `app.yaml` | ||||
| ```yaml | ||||
| application: my-application-id  # defined when you create your app using google dev console | ||||
| module: default                 # see https://cloud.google.com/appengine/docs/go/ | ||||
| version: alpha                  # you can run multiple versions of an app and A/B test | ||||
| runtime: go                     # see https://cloud.google.com/appengine/docs/go/ | ||||
| api_version: go1                # used when appengine supports different go versions | ||||
| `app-engine.yaml` | ||||
|  | ||||
| default_expiration: "1d"        # for CDN serving of static files (use url versioning if long!) | ||||
|  | ||||
| handlers: | ||||
| # all the static files that we normally serve ourselves are defined here and Google will handle | ||||
| # serving them for us from it's own CDN / edge locations. For all the configuration options see: | ||||
| # https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers | ||||
| - url: / | ||||
|   mime_type: text/html | ||||
|   static_files: public/index.html | ||||
|   upload: public/index.html | ||||
|  | ||||
| - url: /favicon.ico | ||||
|   mime_type: image/x-icon | ||||
|   static_files: public/favicon.ico | ||||
|   upload: public/favicon.ico | ||||
|  | ||||
| - url: /scripts | ||||
|   mime_type: text/javascript | ||||
|   static_dir: public/scripts | ||||
|  | ||||
| # static files normally don't touch the server that the app runs on but server-side template files | ||||
| # needs to be readable by the app. The application_readable option makes sure they are available as | ||||
| # part of the app deployment onto the instance. | ||||
| - url: /templates | ||||
|   static_dir: /templates | ||||
|   application_readable: true | ||||
|  | ||||
| # finally, we route all other requests to our application. The script name just means "the go app" | ||||
| - url: /.* | ||||
|   script: _go_app | ||||
| ``` | ||||
| {{< embed "google-app-engine/app-engine.yaml" >}} | ||||
|  | ||||
| #### Router configuration | ||||
|  | ||||
| @@ -177,26 +94,8 @@ that GAE classic provides a wrapper to handle serving the app so instead of a `m | ||||
| we run the server, we instead wire up the router to the default `http.Handler` instead. | ||||
|  | ||||
| `app-engine.go` | ||||
| ```go | ||||
| // +build appengine | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
|     "net/http" | ||||
|     "github.com/labstack/echo" | ||||
| ) | ||||
|  | ||||
| func createMux() *echo.Echo { | ||||
|     e := echo.New() | ||||
|  | ||||
|     // note: we don't need to provide the middleware or static handlers, that's taken care of by the platform | ||||
|     // app engine has it's own "main" wrapper - we just need to hook echo into the default handler | ||||
|     http.Handle("/", e) | ||||
|  | ||||
|     return e | ||||
| } | ||||
| ``` | ||||
| {{< embed "google-app-engine/app-engine.go" >}} | ||||
|  | ||||
| Managed VMs are slightly different. They are expected to respond to requests on port 8080 as well | ||||
| as special health-check requests used by the service to detect if an instance is still running in | ||||
| @@ -204,37 +103,8 @@ order to provide automated failover and instance replacement. The `google.golang | ||||
| package provides this for us so we have a slightly different version for Managed VMs: | ||||
|  | ||||
| `app-managed.go` | ||||
| ```go | ||||
| // +build appenginevm | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
|     "runtime" | ||||
|     "net/http" | ||||
|     "github.com/labstack/echo" | ||||
|     "google.golang.org/appengine" | ||||
| ) | ||||
|  | ||||
| func createMux() *echo.Echo { | ||||
|     // we're in a container on a Google Compute Engine instance so are not sandboxed anymore ... | ||||
|     runtime.GOMAXPROCS(runtime.NumCPU()) | ||||
|  | ||||
|     e := echo.New() | ||||
|  | ||||
|     // note: we don't need to provide the middleware or static handlers | ||||
|     // for the appengine vm version - that's taken care of by the platform | ||||
|  | ||||
|     return e | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|     // the appengine package provides a convenient method to handle the health-check requests | ||||
|     // and also run the app on the correct port. We just need to add Echo to the default handler | ||||
|     http.Handle("/", e) | ||||
|     appengine.Main() | ||||
| } | ||||
| ``` | ||||
| {{< embed "google-app-engine/app-managed.go" >}} | ||||
|  | ||||
| So now we have three different configurations. We can build and run our app as normal so it can | ||||
| be executed locally, on a full Compute Engine instance or any other traditional hosting provider | ||||
|   | ||||
| @@ -1,60 +1,22 @@ | ||||
| --- | ||||
| title: Graceful Shutdown | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 13 | ||||
| --- | ||||
|  | ||||
| ### With [graceful](https://github.com/tylerb/graceful) | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/labstack/echo" | ||||
| 	"github.com/tylerb/graceful" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// Setup | ||||
| 	e := echo.New() | ||||
| 	e.Get("/", func(c *echo.Context) error { | ||||
| 		return c.String(http.StatusOK, "Sue sews rose on slow jor crows nose") | ||||
| 	}) | ||||
|  | ||||
| 	graceful.ListenAndServe(e.Server(":1323"), 5*time.Second) | ||||
| } | ||||
| ``` | ||||
| {{< embed "graceful-shutdown/grace/server.go" >}} | ||||
|  | ||||
| ### With [grace](https://github.com/facebookgo/grace) | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/facebookgo/grace/gracehttp" | ||||
| 	"github.com/labstack/echo" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// Setup | ||||
| 	e := echo.New() | ||||
| 	e.Get("/", func(c *echo.Context) error { | ||||
| 		return c.String(http.StatusOK, "Six sick bricks tick") | ||||
| 	}) | ||||
|  | ||||
| 	gracehttp.Serve(e.Server(":1323")) | ||||
| } | ||||
| ``` | ||||
| {{< embed "graceful-shutdown/graceful/server.go" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
|   | ||||
							
								
								
									
										19
									
								
								website/content/recipes/hello-world.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								website/content/recipes/hello-world.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| --- | ||||
| title: Hello World | ||||
| menu: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 1 | ||||
| --- | ||||
|  | ||||
| ### Server | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| {{< embed "hello-world/server.go" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/hello-world) | ||||
| @@ -1,8 +1,9 @@ | ||||
| --- | ||||
| title: JSONP | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 6 | ||||
| --- | ||||
|  | ||||
| JSONP is a method that allows cross-domain server calls. You can read more about it at the JSON versus JSONP Tutorial. | ||||
| @@ -11,85 +12,16 @@ JSONP is a method that allows cross-domain server calls. You can read more about | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/labstack/echo" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// Setup | ||||
| 	e := echo.New() | ||||
| 	e.ServeDir("/", "public") | ||||
|  | ||||
| 	e.Get("/jsonp", func(c *echo.Context) error { | ||||
| 		callback := c.Query("callback") | ||||
| 		var content struct { | ||||
| 			Response  string    `json:"response"` | ||||
| 			Timestamp time.Time `json:"timestamp"` | ||||
| 			Random    int       `json:"random"` | ||||
| 		} | ||||
| 		content.Response = "Sent via JSONP" | ||||
| 		content.Timestamp = time.Now().UTC() | ||||
| 		content.Random = rand.Intn(1000) | ||||
| 		return c.JSONP(http.StatusOK, callback, &content) | ||||
| 	}) | ||||
|  | ||||
| 	// Start server | ||||
| 	e.Run(":3999") | ||||
| } | ||||
| ``` | ||||
| {{< embed "jsonp/server.go" >}} | ||||
|  | ||||
| ### Client | ||||
|  | ||||
| `index.html` | ||||
|  | ||||
| ```html | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|  | ||||
| <head> | ||||
|     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> | ||||
|     <title>JSONP</title> | ||||
|     <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> | ||||
|     <script type="text/javascript"> | ||||
|         var host_prefix = 'http://localhost:3999'; | ||||
|         $(document).ready(function() { | ||||
|             // JSONP version - add 'callback=?' to the URL - fetch the JSONP response to the request | ||||
|             $("#jsonp-button").click(function(e) { | ||||
|                 e.preventDefault(); | ||||
|                 // The only difference on the client end is the addition of 'callback=?' to the URL | ||||
|                 var url = host_prefix + '/jsonp?callback=?'; | ||||
|                 $.getJSON(url, function(jsonp) { | ||||
|                     console.log(jsonp); | ||||
|                     $("#jsonp-response").html(JSON.stringify(jsonp, null, 2)); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     </script> | ||||
|  | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|     <div class="container" style="margin-top: 50px;"> | ||||
|         <input type="button" class="btn btn-primary btn-lg" id="jsonp-button" value="Get JSONP response"> | ||||
|         <p> | ||||
|             <pre id="jsonp-response"></pre> | ||||
|         </p> | ||||
|     </div> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
| ``` | ||||
| {{< embed "jsonp/public/index.html" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [willf](http://github.com/willf) | ||||
| - [willf](https://github.com/willf) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/jsonp) | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| --- | ||||
| title: JWT Authentication | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 11 | ||||
| --- | ||||
|  | ||||
| Most applications dealing with client authentication will require a more secure | ||||
| @@ -19,85 +20,7 @@ other algorithms are available. | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/dgrijalva/jwt-go" | ||||
| 	"github.com/labstack/echo" | ||||
| 	mw "github.com/labstack/echo/middleware" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	Bearer     = "Bearer" | ||||
| 	SigningKey = "somethingsupersecret" | ||||
| ) | ||||
|  | ||||
| // A JSON Web Token middleware | ||||
| func JWTAuth(key string) echo.HandlerFunc { | ||||
| 	return func(c *echo.Context) error { | ||||
|  | ||||
| 		// Skip WebSocket | ||||
| 		if (c.Request().Header.Get(echo.Upgrade)) == echo.WebSocket { | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		auth := c.Request().Header.Get("Authorization") | ||||
| 		l := len(Bearer) | ||||
| 		he := echo.NewHTTPError(http.StatusUnauthorized) | ||||
|  | ||||
| 		if len(auth) > l+1 && auth[:l] == Bearer { | ||||
| 			t, err := jwt.Parse(auth[l+1:], func(token *jwt.Token) (interface{}, error) { | ||||
|  | ||||
| 				// Always check the signing method | ||||
| 				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { | ||||
| 					return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) | ||||
| 				} | ||||
|  | ||||
| 				// Return the key for validation | ||||
| 				return []byte(key), nil | ||||
| 			}) | ||||
| 			if err == nil && t.Valid { | ||||
| 				// Store token claims in echo.Context | ||||
| 				c.Set("claims", t.Claims) | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		return he | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func accessible(c *echo.Context) error { | ||||
| 	return c.String(http.StatusOK, "No auth required for this route.\n") | ||||
| } | ||||
|  | ||||
| func restricted(c *echo.Context) error { | ||||
| 	return c.String(http.StatusOK, "Access granted with JWT.\n") | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|  | ||||
| 	// Echo instance | ||||
| 	e := echo.New() | ||||
|  | ||||
| 	// Logger | ||||
| 	e.Use(mw.Logger()) | ||||
|  | ||||
| 	// Unauthenticated route | ||||
| 	e.Get("/", accessible) | ||||
|  | ||||
| 	// Restricted group | ||||
| 	r := e.Group("/restricted") | ||||
| 	r.Use(JWTAuth(SigningKey)) | ||||
| 	r.Get("", restricted) | ||||
|  | ||||
| 	// Start server | ||||
| 	e.Run(":1323") | ||||
| } | ||||
| ``` | ||||
| {{< embed "jwt-authentication/server.go" >}} | ||||
|  | ||||
| Run `server.go` and making a request to the root path `/` returns a 200 OK response, | ||||
| as this route does not use our JWT authentication middleware. Sending requests to | ||||
| @@ -121,32 +44,7 @@ $  curl localhost:1323/restricted -H "Authorization: Bearer InvalidToken" => Una | ||||
| Running `token.go` (source) will print JWT that is valid against this middleware | ||||
| to stdout. You can use this token to test succesful authentication on the `/restricted` path. | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/dgrijalva/jwt-go" | ||||
| ) | ||||
|  | ||||
| const SigningKey = "somethingsupersecret" | ||||
|  | ||||
| func main() { | ||||
|  | ||||
| 	// New web token. | ||||
| 	token := jwt.New(jwt.SigningMethodHS256) | ||||
|  | ||||
| 	// Set a header and a claim | ||||
| 	token.Header["typ"] = "JWT" | ||||
| 	token.Claims["exp"] = time.Now().Add(time.Hour * 96).Unix() | ||||
|  | ||||
| 	// Generate encoded token | ||||
| 	t, _ := token.SignedString([]byte(SigningKey)) | ||||
| 	fmt.Println(t) | ||||
| } | ||||
| ``` | ||||
| {{< embed "jwt-authentication/token/token.go" >}} | ||||
|  | ||||
| ```sh | ||||
| # Valid token | ||||
| @@ -155,6 +53,6 @@ $  curl localhost:1323/restricted -H "Authorization: Bearer <token>" => Access g | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [axdg](http://github.com/axdg) | ||||
| - [axdg](https://github.com/axdg) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/jwt-authentication) | ||||
|   | ||||
							
								
								
									
										20
									
								
								website/content/recipes/middleware.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								website/content/recipes/middleware.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| --- | ||||
| title: Middleware | ||||
| menu: | ||||
|   side: | ||||
|     identifier: "recipe-miiddleware" | ||||
|     parent: recipes | ||||
|     weight: 3 | ||||
| --- | ||||
|  | ||||
| ### Server | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| {{< embed "middleware/server.go" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/middleware) | ||||
| @@ -1,8 +1,9 @@ | ||||
| --- | ||||
| title: Streaming File Upload | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 8 | ||||
| --- | ||||
|  | ||||
| - Streaming multipart/form-data file upload | ||||
| @@ -12,114 +13,16 @@ menu: | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
|  | ||||
| 	"github.com/labstack/echo" | ||||
| 	mw "github.com/labstack/echo/middleware" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| func upload(c *echo.Context) error { | ||||
| 	mr, err := c.Request().MultipartReader() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Read form field `name` | ||||
| 	part, err := mr.NextPart() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer part.Close() | ||||
| 	b, err := ioutil.ReadAll(part) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	name := string(b) | ||||
|  | ||||
| 	// Read form field `email` | ||||
| 	part, err = mr.NextPart() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer part.Close() | ||||
| 	b, err = ioutil.ReadAll(part) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	email := string(b) | ||||
|  | ||||
| 	// Read files | ||||
| 	i := 0 | ||||
| 	for { | ||||
| 		part, err := mr.NextPart() | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				break | ||||
| 			} | ||||
| 			return err | ||||
| 		} | ||||
| 		defer part.Close() | ||||
|  | ||||
| 		file, err := os.Create(part.FileName()) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer file.Close() | ||||
|  | ||||
| 		if _, err := io.Copy(file, part); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		i++ | ||||
| 	} | ||||
| 	return c.String(http.StatusOK, "Thank You! %s <%s>, %d files uploaded successfully.", | ||||
| 		name, email, i) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	e := echo.New() | ||||
| 	e.Use(mw.Logger()) | ||||
| 	e.Use(mw.Recover()) | ||||
|  | ||||
| 	e.Static("/", "public") | ||||
| 	e.Post("/upload", upload) | ||||
|  | ||||
| 	e.Run(":1323") | ||||
| } | ||||
| ``` | ||||
| {{< embed "streaming-file-upload/server.go" >}} | ||||
|  | ||||
| ### Client | ||||
|  | ||||
| `index.html` | ||||
|  | ||||
| ```html | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <title>File Upload</title> | ||||
| </head> | ||||
| <body> | ||||
|     <h1>Upload Files</h1> | ||||
|     <form action="/upload" method="post" enctype="multipart/form-data"> | ||||
|         Name: <input type="text" name="name"><br> | ||||
|         Email: <input type="email" name="email"><br> | ||||
|         Files: <input type="file" name="files" multiple><br><br> | ||||
|         <input type="submit" value="Submit"> | ||||
|     </form> | ||||
| </body> | ||||
| </html> | ||||
|  | ||||
| ``` | ||||
| {{< embed "streaming-file-upload/public/index.html" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](http://github.com/vishr) | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-file-upload) | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| --- | ||||
| title: Streaming Response | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 9 | ||||
| --- | ||||
|  | ||||
| - Send data as it is produced | ||||
| @@ -12,53 +13,7 @@ menu: | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"github.com/labstack/echo" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Geolocation struct { | ||||
| 		Altitude  float64 | ||||
| 		Latitude  float64 | ||||
| 		Longitude float64 | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	locations = []Geolocation{ | ||||
| 		{-97, 37.819929, -122.478255}, | ||||
| 		{1899, 39.096849, -120.032351}, | ||||
| 		{2619, 37.865101, -119.538329}, | ||||
| 		{42, 33.812092, -117.918974}, | ||||
| 		{15, 37.77493, -122.419416}, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	e := echo.New() | ||||
| 	e.Get("/", func(c *echo.Context) error { | ||||
| 		c.Response().Header().Set(echo.ContentType, echo.ApplicationJSON) | ||||
| 		c.Response().WriteHeader(http.StatusOK) | ||||
| 		for _, l := range locations { | ||||
| 			if err := json.NewEncoder(c.Response()).Encode(l); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			c.Response().Flush() | ||||
| 			time.Sleep(1 * time.Second) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	e.Run(":1323") | ||||
| } | ||||
| ``` | ||||
| {{< embed "streaming-response/server.go" >}} | ||||
|  | ||||
| ### Client | ||||
|  | ||||
| @@ -78,6 +33,6 @@ $ curl localhost:1323 | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](http://github.com/vishr) | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-response) | ||||
|   | ||||
| @@ -1,85 +1,18 @@ | ||||
| --- | ||||
| title: Subdomains | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 10  | ||||
| --- | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/labstack/echo" | ||||
| 	mw "github.com/labstack/echo/middleware" | ||||
| ) | ||||
|  | ||||
| type Hosts map[string]http.Handler | ||||
|  | ||||
| func (h Hosts) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	if handler := h[r.Host]; handler != nil { | ||||
| 		handler.ServeHTTP(w, r) | ||||
| 	} else { | ||||
| 		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	// Host map | ||||
| 	hosts := make(Hosts) | ||||
|  | ||||
| 	//----- | ||||
| 	// API | ||||
| 	//----- | ||||
|  | ||||
| 	api := echo.New() | ||||
| 	api.Use(mw.Logger()) | ||||
| 	api.Use(mw.Recover()) | ||||
|  | ||||
| 	hosts["api.localhost:1323"] = api | ||||
|  | ||||
| 	api.Get("/", func(c *echo.Context) error { | ||||
| 		return c.String(http.StatusOK, "API") | ||||
| 	}) | ||||
|  | ||||
| 	//------ | ||||
| 	// Blog | ||||
| 	//------ | ||||
|  | ||||
| 	blog := echo.New() | ||||
| 	blog.Use(mw.Logger()) | ||||
| 	blog.Use(mw.Recover()) | ||||
|  | ||||
| 	hosts["blog.localhost:1323"] = blog | ||||
|  | ||||
| 	blog.Get("/", func(c *echo.Context) error { | ||||
| 		return c.String(http.StatusOK, "Blog") | ||||
| 	}) | ||||
|  | ||||
| 	//--------- | ||||
| 	// Website | ||||
| 	//--------- | ||||
|  | ||||
| 	site := echo.New() | ||||
| 	site.Use(mw.Logger()) | ||||
| 	site.Use(mw.Recover()) | ||||
|  | ||||
| 	hosts["localhost:1323"] = site | ||||
|  | ||||
| 	site.Get("/", func(c *echo.Context) error { | ||||
| 		return c.String(http.StatusOK, "Welcome!") | ||||
| 	}) | ||||
|  | ||||
| 	http.ListenAndServe(":1323", hosts) | ||||
| } | ||||
| ``` | ||||
| {{< embed "subdomains/server.go" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [axdg](http://github.com/axdg) | ||||
| - [vishr](http://github.com/axdg) | ||||
| - [axdg](https://github.com/axdg) | ||||
| - [vishr](https://github.com/axdg) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/subdomains) | ||||
|   | ||||
							
								
								
									
										25
									
								
								website/content/recipes/website.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								website/content/recipes/website.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| --- | ||||
| title: Website | ||||
| menu: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 4 | ||||
| --- | ||||
|  | ||||
| ### Server | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| {{< embed "website/server.go" >}} | ||||
|  | ||||
| ### Client | ||||
|  | ||||
| `index.html` | ||||
|  | ||||
| {{< embed "website/public/index.html" >}} | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/website) | ||||
| @@ -1,94 +1,22 @@ | ||||
| --- | ||||
| title: WebSocket | ||||
| menu: | ||||
|   main: | ||||
|   side: | ||||
|     parent: recipes | ||||
|     weight: 5 | ||||
| --- | ||||
|  | ||||
| ### Server | ||||
|  | ||||
| `server.go` | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/labstack/echo" | ||||
| 	mw "github.com/labstack/echo/middleware" | ||||
| 	"golang.org/x/net/websocket" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	e := echo.New() | ||||
|  | ||||
| 	e.Use(mw.Logger()) | ||||
| 	e.Use(mw.Recover()) | ||||
|  | ||||
| 	e.Static("/", "public") | ||||
| 	e.WebSocket("/ws", func(c *echo.Context) (err error) { | ||||
| 		ws := c.Socket() | ||||
| 		msg := "" | ||||
|  | ||||
| 		for { | ||||
| 			if err = websocket.Message.Send(ws, "Hello, Client!"); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			if err = websocket.Message.Receive(ws, &msg); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			fmt.Println(msg) | ||||
| 		} | ||||
| 		return | ||||
| 	}) | ||||
|  | ||||
| 	e.Run(":1323") | ||||
| } | ||||
| ``` | ||||
| {{< embed "websocket/server.go" >}} | ||||
|  | ||||
| ### Client | ||||
|  | ||||
| `index.html` | ||||
|  | ||||
| ```html | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <title>WebSocket</title> | ||||
| </head> | ||||
| <body> | ||||
|     <p id="output"></p> | ||||
|  | ||||
|     <script> | ||||
|         var loc = window.location; | ||||
|         var uri = 'ws:'; | ||||
|  | ||||
|         if (loc.protocol === 'https:') { | ||||
|             uri = 'wss:'; | ||||
|         } | ||||
|         uri += '//' + loc.host; | ||||
|         uri += loc.pathname + 'ws'; | ||||
|  | ||||
|         ws = new WebSocket(uri) | ||||
|  | ||||
|         ws.onopen = function() { | ||||
|             console.log('Connected') | ||||
|         } | ||||
|  | ||||
|         ws.onmessage = function(evt) { | ||||
|             var out = document.getElementById('output'); | ||||
|             out.innerHTML += evt.data + '<br>'; | ||||
|         } | ||||
|  | ||||
|         setInterval(function() { | ||||
|             ws.send('Hello, Server!'); | ||||
|         }, 1000); | ||||
|     </script> | ||||
| </body> | ||||
| </html> | ||||
| ``` | ||||
| {{< embed "websocket/public/index.html" >}} | ||||
|  | ||||
| ### Output | ||||
|  | ||||
| @@ -114,6 +42,6 @@ Hello, Server! | ||||
|  | ||||
| ### Maintainers | ||||
|  | ||||
| - [vishr](http://github.com/vishr) | ||||
| - [vishr](https://github.com/vishr) | ||||
|  | ||||
| ### [Source Code](https://github.com/labstack/echo/blob/master/recipes/websocket) | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
|                             <section class="content"> | ||||
|                                 {{ .Content }} | ||||
|                             </section> | ||||
|                             <footer> | ||||
|                             <footer style="margin-top: 80px;"> | ||||
|                                 <div id="disqus_thread"></div> | ||||
|                                 <script type="text/javascript"> | ||||
|                                     /* * * CONFIGURATION VARIABLES * * */ | ||||
|   | ||||
| @@ -27,9 +27,10 @@ | ||||
|         </ul> | ||||
|     </div> | ||||
| </footer> | ||||
| <script src="/scripts/highlight.pack.min.js"></script> | ||||
| <!-- <script src="/scripts/highlight.pack.min.js"></script> --> | ||||
| <script src="/scripts/prism.js"></script> | ||||
| <script src="/scripts/echo.js"></script> | ||||
| <script>hljs.initHighlightingOnLoad();</script> | ||||
| <!-- <script>hljs.initHighlightingOnLoad();</script> --> | ||||
| <script> | ||||
|     (function(i, s, o, g, r, a, m) { | ||||
|         i['GoogleAnalyticsObject'] = r; | ||||
|   | ||||
| @@ -16,7 +16,8 @@ | ||||
|     <link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons"> | ||||
|     <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700"> | ||||
|     <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"> | ||||
|     <link rel="stylesheet" href="/styles/monokai.css"> | ||||
|     <!-- <link rel="stylesheet" href="/styles/monokai.css"> --> | ||||
|     <link rel="stylesheet" href="/styles/prism.css"> | ||||
|     <link rel="stylesheet" href="/styles/echo.css"> | ||||
|     <script src="//storage.googleapis.com/code.getmdl.io/1.0.5/material.min.js"></script> | ||||
| </head> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <aside class="menu"> | ||||
|     <div> | ||||
|         {{ range .Site.Menus.main }} | ||||
|         {{ range .Site.Menus.side }} | ||||
|             {{ if .HasChildren }} | ||||
|                 <h4> | ||||
|                     {{ .Pre }} | ||||
|   | ||||
							
								
								
									
										2
									
								
								website/layouts/shortcodes/embed.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								website/layouts/shortcodes/embed.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| <pre data-src="https://raw.githubusercontent.com/labstack/echo/master/recipes/{{ .Get 0 }}"> | ||||
| </pre> | ||||
							
								
								
									
										9
									
								
								website/static/scripts/highlight.pack.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								website/static/scripts/highlight.pack.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								website/static/scripts/prism.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								website/static/scripts/prism.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| /* http://prismjs.com/download.html?themes=prism-coy&languages=markup+css+clike+javascript+go&plugins=file-highlight */ | ||||
| var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=t.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return t.util.clone(e)})}return e}},languages:{extend:function(e,n){var a=t.util.clone(t.languages[e]);for(var r in n)a[r]=n[r];return a},insertBefore:function(e,n,a,r){r=r||t.languages;var l=r[e];if(2==arguments.length){a=arguments[1];for(var i in a)a.hasOwnProperty(i)&&(l[i]=a[i]);return l}var o={};for(var s in l)if(l.hasOwnProperty(s)){if(s==n)for(var i in a)a.hasOwnProperty(i)&&(o[i]=a[i]);o[s]=l[s]}return t.languages.DFS(t.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,n,a){for(var r in e)e.hasOwnProperty(r)&&(n.call(e,r,e[r],a||r),"Object"===t.util.type(e[r])?t.languages.DFS(e[r],n):"Array"===t.util.type(e[r])&&t.languages.DFS(e[r],n,r))}},plugins:{},highlightAll:function(e,n){for(var a,r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'),l=0;a=r[l++];)t.highlightElement(a,e===!0,n)},highlightElement:function(n,a,r){for(var l,i,o=n;o&&!e.test(o.className);)o=o.parentNode;o&&(l=(o.className.match(e)||[,""])[1],i=t.languages[l]),n.className=n.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,o=n.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var s=n.textContent,u={element:n,language:l,grammar:i,code:s};if(!s||!i)return t.hooks.run("complete",u),void 0;if(t.hooks.run("before-highlight",u),a&&_self.Worker){var g=new Worker(t.filename);g.onmessage=function(e){u.highlightedCode=e.data,t.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),t.hooks.run("after-highlight",u),t.hooks.run("complete",u)},g.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=t.highlight(u.code,u.grammar,u.language),t.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(n),t.hooks.run("after-highlight",u),t.hooks.run("complete",u)},highlight:function(e,a,r){var l=t.tokenize(e,a);return n.stringify(t.util.encode(l),r)},tokenize:function(e,n){var a=t.Token,r=[e],l=n.rest;if(l){for(var i in l)n[i]=l[i];delete n.rest}e:for(var i in n)if(n.hasOwnProperty(i)&&n[i]){var o=n[i];o="Array"===t.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],g=u.inside,c=!!u.lookbehind,f=0,h=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var d=r[p];if(r.length>e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),P=[p,1];b&&P.push(b);var A=new a(i,g?t.tokenize(m,g):m,h);P.push(A),w&&P.push(w),Array.prototype.splice.apply(r,P)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var l={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}t.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+"</"+l.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,l=n.immediateClose;_self.postMessage(t.highlight(r,t.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); | ||||
| Prism.languages.markup={comment:/<!--[\w\W]*?-->/,prolog:/<\?[\w\W]+?\?>/,doctype:/<!DOCTYPE[\w\W]+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[^\s>\/=.]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; | ||||
| Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<style[\w\W]*?>)[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); | ||||
| Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; | ||||
| Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<script[\w\W]*?>)[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; | ||||
| Prism.languages.go=Prism.languages.extend("clike",{keyword:/\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,builtin:/\b(bool|byte|complex(64|128)|error|float(32|64)|rune|string|u?int(8|16|32|64|)|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(ln)?|real|recover)\b/,"boolean":/\b(_|iota|nil|true|false)\b/,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,number:/\b(-?(0x[a-f\d]+|(\d+\.?\d*|\.\d+)(e[-+]?\d+)?)i?)\b/i,string:/("|'|`)(\\?.|\r|\n)*?\1/}),delete Prism.languages.go["class-name"]; | ||||
| !function(){"undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector&&(self.Prism.fileHighlight=function(){var e={js:"javascript",html:"markup",svg:"markup",xml:"markup",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell"};Array.prototype.forEach&&Array.prototype.slice.call(document.querySelectorAll("pre[data-src]")).forEach(function(t){for(var r,a=t.getAttribute("data-src"),s=t,n=/\blang(?:uage)?-(?!\*)(\w+)\b/i;s&&!n.test(s.className);)s=s.parentNode;if(s&&(r=(t.className.match(n)||[,""])[1]),!r){var o=(a.match(/\.(\w+)$/)||[,""])[1];r=e[o]||o}var l=document.createElement("code");l.className="language-"+r,t.textContent="",l.textContent="Loading…",t.appendChild(l);var i=new XMLHttpRequest;i.open("GET",a,!0),i.onreadystatechange=function(){4==i.readyState&&(i.status<400&&i.responseText?(l.textContent=i.responseText,Prism.highlightElement(l)):l.textContent=i.status>=400?"✖ Error "+i.status+" while fetching file: "+i.statusText:"✖ Error: File does not exist or is empty")},i.send(null)})},self.Prism.fileHighlight())}(); | ||||
| @@ -14,7 +14,7 @@ footer { | ||||
| .github a:hover { | ||||
|     color: #333; | ||||
| } | ||||
| code { | ||||
| :not(pre) > code { | ||||
|     padding: 2px 4px; | ||||
|     background: #EEE; | ||||
|     color: #424242; | ||||
|   | ||||
| @@ -1,126 +0,0 @@ | ||||
| /* | ||||
| Monokai style - ported by Luigi Maselli - http://grigio.org | ||||
| */ | ||||
|  | ||||
| .hljs { | ||||
|   display: block; | ||||
|   overflow-x: auto; | ||||
|   padding: 0.5em; | ||||
|   background: #272822; | ||||
|   -webkit-text-size-adjust: none; | ||||
| } | ||||
|  | ||||
| .hljs-tag, | ||||
| .hljs-tag .hljs-title, | ||||
| .hljs-keyword, | ||||
| .hljs-literal, | ||||
| .hljs-strong, | ||||
| .hljs-change, | ||||
| .hljs-winutils, | ||||
| .hljs-flow, | ||||
| .nginx .hljs-title, | ||||
| .tex .hljs-special { | ||||
|   color: #f92672; | ||||
| } | ||||
|  | ||||
| .hljs { | ||||
|   color: #ddd; | ||||
| } | ||||
|  | ||||
| .hljs .hljs-constant, | ||||
| .asciidoc .hljs-code, | ||||
| .markdown .hljs-code { | ||||
|   color: #66d9ef; | ||||
| } | ||||
|  | ||||
| .hljs-code, | ||||
| .hljs-class .hljs-title, | ||||
| .hljs-header { | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| .hljs-link_label, | ||||
| .hljs-attribute, | ||||
| .hljs-symbol, | ||||
| .hljs-symbol .hljs-string, | ||||
| .hljs-value, | ||||
| .hljs-regexp { | ||||
|   color: #bf79db; | ||||
| } | ||||
|  | ||||
| .hljs-link_url, | ||||
| .hljs-tag .hljs-value, | ||||
| .hljs-string, | ||||
| .hljs-bullet, | ||||
| .hljs-subst, | ||||
| .hljs-title, | ||||
| .hljs-emphasis, | ||||
| .hljs-type, | ||||
| .hljs-preprocessor, | ||||
| .hljs-pragma, | ||||
| .ruby .hljs-class .hljs-parent, | ||||
| .hljs-built_in, | ||||
| .django .hljs-template_tag, | ||||
| .django .hljs-variable, | ||||
| .smalltalk .hljs-class, | ||||
| .django .hljs-filter .hljs-argument, | ||||
| .smalltalk .hljs-localvars, | ||||
| .smalltalk .hljs-array, | ||||
| .hljs-attr_selector, | ||||
| .hljs-pseudo, | ||||
| .hljs-addition, | ||||
| .hljs-stream, | ||||
| .hljs-envvar, | ||||
| .apache .hljs-tag, | ||||
| .apache .hljs-cbracket, | ||||
| .tex .hljs-command, | ||||
| .hljs-prompt, | ||||
| .hljs-name { | ||||
|   color: #a6e22e; | ||||
| } | ||||
|  | ||||
| .hljs-comment, | ||||
| .hljs-annotation, | ||||
| .smartquote, | ||||
| .hljs-blockquote, | ||||
| .hljs-horizontal_rule, | ||||
| .hljs-decorator, | ||||
| .hljs-pi, | ||||
| .hljs-doctype, | ||||
| .hljs-deletion, | ||||
| .hljs-shebang, | ||||
| .apache .hljs-sqbracket, | ||||
| .tex .hljs-formula { | ||||
|   color: #75715e; | ||||
| } | ||||
|  | ||||
| .hljs-keyword, | ||||
| .hljs-literal, | ||||
| .css .hljs-id, | ||||
| .hljs-doctag, | ||||
| .hljs-title, | ||||
| .hljs-header, | ||||
| .hljs-type, | ||||
| .vbscript .hljs-built_in, | ||||
| .rsl .hljs-built_in, | ||||
| .smalltalk .hljs-class, | ||||
| .diff .hljs-header, | ||||
| .hljs-chunk, | ||||
| .hljs-winutils, | ||||
| .bash .hljs-variable, | ||||
| .apache .hljs-tag, | ||||
| .tex .hljs-special, | ||||
| .hljs-request, | ||||
| .hljs-status { | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| .coffeescript .javascript, | ||||
| .javascript .xml, | ||||
| .tex .hljs-formula, | ||||
| .xml .javascript, | ||||
| .xml .vbscript, | ||||
| .xml .css, | ||||
| .xml .hljs-cdata { | ||||
|   opacity: 0.5; | ||||
| } | ||||
							
								
								
									
										236
									
								
								website/static/styles/prism.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								website/static/styles/prism.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| /* http://prismjs.com/download.html?themes=prism-coy&languages=markup+css+clike+javascript+go&plugins=file-highlight */ | ||||
| /** | ||||
|  * prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML | ||||
|  * Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics); | ||||
|  * @author Tim  Shedor | ||||
|  */ | ||||
|  | ||||
| code[class*="language-"], | ||||
| pre[class*="language-"] { | ||||
| 	color: black; | ||||
| 	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; | ||||
| 	direction: ltr; | ||||
| 	text-align: left; | ||||
| 	white-space: pre; | ||||
| 	word-spacing: normal; | ||||
| 	word-break: normal; | ||||
| 	word-wrap: normal; | ||||
| 	line-height: 1.5; | ||||
|  | ||||
| 	-moz-tab-size: 4; | ||||
| 	-o-tab-size: 4; | ||||
| 	tab-size: 4; | ||||
|  | ||||
| 	-webkit-hyphens: none; | ||||
| 	-moz-hyphens: none; | ||||
| 	-ms-hyphens: none; | ||||
| 	hyphens: none; | ||||
| } | ||||
|  | ||||
| /* Code blocks */ | ||||
| pre[class*="language-"] { | ||||
| 	position: relative; | ||||
| 	margin: .5em 0; | ||||
| 	-webkit-box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; | ||||
| 	-moz-box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; | ||||
| 	box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; | ||||
| 	border-left: 10px solid #358ccb; | ||||
| 	background-color: #fdfdfd; | ||||
| 	background-image: -webkit-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); | ||||
| 	background-image: -moz-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); | ||||
| 	background-image: -ms-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); | ||||
| 	background-image: -o-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); | ||||
| 	background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); | ||||
| 	background-size: 3em 3em; | ||||
| 	background-origin: content-box; | ||||
| 	overflow: visible; | ||||
| 	max-height: 30em; | ||||
| 	padding: 0; | ||||
| } | ||||
|  | ||||
| code[class*="language"] { | ||||
| 	max-height: inherit; | ||||
| 	height: 100%; | ||||
| 	padding: 0 1em; | ||||
| 	display: block; | ||||
| 	overflow: auto; | ||||
| } | ||||
|  | ||||
| /* Margin bottom to accomodate shadow */ | ||||
| :not(pre) > code[class*="language-"], | ||||
| pre[class*="language-"] { | ||||
| 	background-color: #fdfdfd; | ||||
| 	-webkit-box-sizing: border-box; | ||||
| 	-moz-box-sizing: border-box; | ||||
| 	box-sizing: border-box; | ||||
| 	margin-bottom: 1em; | ||||
| } | ||||
|  | ||||
| /* Inline code */ | ||||
| :not(pre) > code[class*="language-"] { | ||||
| 	position: relative; | ||||
| 	padding: .2em; | ||||
| 	-webkit-border-radius: 0.3em; | ||||
| 	-moz-border-radius: 0.3em; | ||||
| 	-ms-border-radius: 0.3em; | ||||
| 	-o-border-radius: 0.3em; | ||||
| 	border-radius: 0.3em; | ||||
| 	color: #c92c2c; | ||||
| 	border: 1px solid rgba(0, 0, 0, 0.1); | ||||
| 	display: inline; | ||||
| } | ||||
|  | ||||
| pre[class*="language-"]:before, | ||||
| pre[class*="language-"]:after { | ||||
| 	content: ''; | ||||
| 	z-index: -2; | ||||
| 	display: block; | ||||
| 	position: absolute; | ||||
| 	bottom: 0.75em; | ||||
| 	left: 0.18em; | ||||
| 	width: 40%; | ||||
| 	height: 20%; | ||||
| 	-webkit-box-shadow: 0px 13px 8px #979797; | ||||
| 	-moz-box-shadow: 0px 13px 8px #979797; | ||||
| 	box-shadow: 0px 13px 8px #979797; | ||||
| 	-webkit-transform: rotate(-2deg); | ||||
| 	-moz-transform: rotate(-2deg); | ||||
| 	-ms-transform: rotate(-2deg); | ||||
| 	-o-transform: rotate(-2deg); | ||||
| 	transform: rotate(-2deg); | ||||
| } | ||||
|  | ||||
| :not(pre) > code[class*="language-"]:after, | ||||
| pre[class*="language-"]:after { | ||||
| 	right: 0.75em; | ||||
| 	left: auto; | ||||
| 	-webkit-transform: rotate(2deg); | ||||
| 	-moz-transform: rotate(2deg); | ||||
| 	-ms-transform: rotate(2deg); | ||||
| 	-o-transform: rotate(2deg); | ||||
| 	transform: rotate(2deg); | ||||
| } | ||||
|  | ||||
| .token.comment, | ||||
| .token.block-comment, | ||||
| .token.prolog, | ||||
| .token.doctype, | ||||
| .token.cdata { | ||||
| 	color: #7D8B99; | ||||
| } | ||||
|  | ||||
| .token.punctuation { | ||||
| 	color: #5F6364; | ||||
| } | ||||
|  | ||||
| .token.property, | ||||
| .token.tag, | ||||
| .token.boolean, | ||||
| .token.number, | ||||
| .token.function-name, | ||||
| .token.constant, | ||||
| .token.symbol, | ||||
| .token.deleted { | ||||
| 	color: #c92c2c; | ||||
| } | ||||
|  | ||||
| .token.selector, | ||||
| .token.attr-name, | ||||
| .token.string, | ||||
| .token.char, | ||||
| .token.function, | ||||
| .token.builtin, | ||||
| .token.inserted { | ||||
| 	color: #2f9c0a; | ||||
| } | ||||
|  | ||||
| .token.operator, | ||||
| .token.entity, | ||||
| .token.url, | ||||
| .token.variable { | ||||
| 	color: #a67f59; | ||||
| 	background: rgba(255, 255, 255, 0.5); | ||||
| } | ||||
|  | ||||
| .token.atrule, | ||||
| .token.attr-value, | ||||
| .token.keyword, | ||||
| .token.class-name { | ||||
| 	color: #1990b8; | ||||
| } | ||||
|  | ||||
| .token.regex, | ||||
| .token.important { | ||||
| 	color: #e90; | ||||
| } | ||||
|  | ||||
| .language-css .token.string, | ||||
| .style .token.string { | ||||
| 	color: #a67f59; | ||||
| 	background: rgba(255, 255, 255, 0.5); | ||||
| } | ||||
|  | ||||
| .token.important { | ||||
| 	font-weight: normal; | ||||
| } | ||||
|  | ||||
| .token.bold { | ||||
| 	font-weight: bold; | ||||
| } | ||||
| .token.italic { | ||||
| 	font-style: italic; | ||||
| } | ||||
|  | ||||
| .token.entity { | ||||
| 	cursor: help; | ||||
| } | ||||
|  | ||||
| .namespace { | ||||
| 	opacity: .7; | ||||
| } | ||||
|  | ||||
| @media screen and (max-width: 767px) { | ||||
| 	pre[class*="language-"]:before, | ||||
| 	pre[class*="language-"]:after { | ||||
| 		bottom: 14px; | ||||
| 		-webkit-box-shadow: none; | ||||
| 		-moz-box-shadow: none; | ||||
| 		box-shadow: none; | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| /* Plugin styles */ | ||||
| .token.tab:not(:empty):before, | ||||
| .token.cr:before, | ||||
| .token.lf:before { | ||||
| 	color: #e0d7d1; | ||||
| } | ||||
|  | ||||
| /* Plugin styles: Line Numbers */ | ||||
| pre[class*="language-"].line-numbers { | ||||
| 	padding-left: 0; | ||||
| } | ||||
|  | ||||
| pre[class*="language-"].line-numbers code { | ||||
| 	padding-left: 3.8em; | ||||
| } | ||||
|  | ||||
| pre[class*="language-"].line-numbers .line-numbers-rows { | ||||
| 	left: 0; | ||||
| } | ||||
|  | ||||
| /* Plugin styles: Line Highlight */ | ||||
| pre[class*="language-"][data-line] { | ||||
| 	padding-top: 0; | ||||
| 	padding-bottom: 0; | ||||
| 	padding-left: 0; | ||||
| } | ||||
| pre[data-line] code { | ||||
| 	position: relative; | ||||
| 	padding-left: 4em; | ||||
| } | ||||
| pre .line-highlight { | ||||
| 	margin-top: 0; | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user