├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app-engine ├── go11x │ ├── app.yaml │ └── hello.go └── gophers │ ├── README.md │ ├── app.yaml │ └── hello.go ├── assets-in-binary ├── README.md ├── assets │ ├── favicon.ico │ └── images │ │ └── example.png ├── main.go └── templates │ ├── foo │ └── bar.tmpl │ └── index.tmpl ├── auto-tls ├── example1 │ └── main.go ├── example2 │ └── main.go └── example3 │ └── main.go ├── basic ├── main.go └── main_test.go ├── cookie ├── README.md └── main.go ├── custom-validation └── server.go ├── file-binding ├── main.go └── public │ └── index.html ├── forward-proxy ├── README.md └── main.go ├── go.mod ├── go.sum ├── graceful-shutdown ├── close │ ├── README.md │ └── server.go └── graceful-shutdown │ ├── README.md │ ├── notify-with-context │ └── server.go │ └── notify-without-context │ └── server.go ├── group-routes ├── README.md ├── main.go └── routes │ ├── main.go │ ├── ping.go │ └── users.go ├── grpc └── example1 │ ├── Makefile │ ├── README.md │ ├── gen │ └── helloworld │ │ └── v1 │ │ ├── helloworld.pb.go │ │ └── helloworld_grpc.pb.go │ ├── gin │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── grpc │ └── server.go │ └── pb │ └── helloworld │ └── v1 │ └── helloworld.proto ├── http-pusher ├── assets │ └── app.js ├── main.go └── testdata │ ├── ca.pem │ ├── server.key │ └── server.pem ├── http2 ├── README.md ├── main.go └── testdata │ ├── ca.pem │ ├── server.key │ └── server.pem ├── multiple-service └── main.go ├── new_relic ├── README.md ├── go.mod ├── go.sum └── main.go ├── oidc ├── README.md ├── go.mod ├── go.sum ├── main.go └── templates │ ├── index.html │ └── success.html ├── otel ├── README.md ├── go.mod ├── go.sum └── main.go ├── ratelimiter ├── README.md └── rate.go ├── realtime-advanced ├── Makefile ├── go.mod ├── go.sum ├── main.go ├── resources │ ├── room_login.templ.html │ └── static │ │ ├── epoch.min.css │ │ ├── epoch.min.js │ │ ├── prismjs.min.css │ │ ├── prismjs.min.js │ │ └── realtime.js ├── rooms.go ├── routes.go └── stats.go ├── realtime-chat ├── Makefile ├── go.mod ├── go.sum ├── main.go ├── rooms.go └── template.go ├── reverse-proxy ├── README.md ├── realServer │ └── main.go └── reverseServer │ └── main.go ├── secure-web-app ├── README.md └── main.go ├── send_chunked_data └── send_chunked_data.go ├── server-sent-event ├── README.md ├── main.go └── public │ └── index.html ├── struct-lvl-validations ├── README.md └── server.go ├── template ├── main.go └── testdata │ └── raw.tmpl ├── upload-file ├── multiple │ ├── main.go │ └── public │ │ └── index.html └── single │ ├── main.go │ └── public │ └── index.html ├── versioning ├── README.md └── main.go └── websocket ├── README.md ├── client └── client.go ├── go.mod ├── go.sum └── server └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for taking the time to join our community and start contributing! 4 | 5 | - With issues: 6 | - Use the search tool before opening a new issue. 7 | - Please provide source code and commit sha if you found a bug. 8 | - Review existing issues and provide feedback or react to them. 9 | 10 | - With pull requests: 11 | - Open your pull request against `master` 12 | - Your pull request should have no more than two commits, if not you should squash them. 13 | - It should pass all tests in the available continuous integrations systems such as TravisCI. 14 | - You should add/modify tests to cover your proposed code changes. 15 | - If your pull request contains a new feature, please document it on the README. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gin-Gonic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gin Examples 2 | 3 | This repository contains a number of ready-to-run examples demonstrating various use cases of [Gin](https://github.com/gin-gonic/gin). 4 | 5 | Refer to the [Gin documentation](https://gin-gonic.com/en/docs/) for how to execute the example tutorials. 6 | 7 | ## Contributing 8 | 9 | Are you missing an example? Please feel free to open an issue or commit one pull request. 10 | 11 | Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on how to contribute. 12 | -------------------------------------------------------------------------------- /app-engine/go11x/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go113 # replace with go112 for Go 1.12 2 | -------------------------------------------------------------------------------- /app-engine/go11x/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func main() { 12 | port := os.Getenv("PORT") 13 | 14 | if port == "" { 15 | port = "8080" 16 | log.Printf("Defaulting to port %s", port) 17 | } 18 | 19 | // Starts a new Gin instance with no middle-ware 20 | r := gin.New() 21 | 22 | // Define handlers 23 | r.GET("/", func(c *gin.Context) { 24 | c.String(http.StatusOK, "Hello World!") 25 | }) 26 | r.GET("/ping", func(c *gin.Context) { 27 | c.String(http.StatusOK, "pong") 28 | }) 29 | 30 | // Listen and serve on defined port 31 | log.Printf("Listening on port %s", port) 32 | r.Run(":" + port) 33 | } 34 | -------------------------------------------------------------------------------- /app-engine/gophers/README.md: -------------------------------------------------------------------------------- 1 | # Guide to run Gin under App Engine LOCAL Development Server 2 | 3 | 1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) 4 | 2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download` 5 | 3. Download Gin source code using: `$ go get github.com/gin-gonic/examples` 6 | 4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/examples/app-engine/` 7 | 5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2) 8 | 9 | -------------------------------------------------------------------------------- /app-engine/gophers/app.yaml: -------------------------------------------------------------------------------- 1 | application: hello 2 | version: 1 3 | runtime: go 4 | api_version: go1 5 | 6 | handlers: 7 | - url: /.* 8 | script: _go_app -------------------------------------------------------------------------------- /app-engine/gophers/hello.go: -------------------------------------------------------------------------------- 1 | package hello 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // This function's name is a must. App Engine uses it to drive the requests properly. 10 | func init() { 11 | // Starts a new Gin instance with no middle-ware 12 | r := gin.New() 13 | 14 | // Define your handlers 15 | r.GET("/", func(c *gin.Context) { 16 | c.String(http.StatusOK, "Hello World!") 17 | }) 18 | r.GET("/ping", func(c *gin.Context) { 19 | c.String(http.StatusOK, "pong") 20 | }) 21 | 22 | // Handle all requests using net/http 23 | http.Handle("/", r) 24 | } 25 | -------------------------------------------------------------------------------- /assets-in-binary/README.md: -------------------------------------------------------------------------------- 1 | # Assets in Binary 2 | 3 | This project demonstrates how to embed static files and file trees into a Go executable using the `//go:embed` directive introduced in Go 1.16. The embedded files can be accessed at runtime, allowing for easy distribution of assets within a single binary. 4 | 5 | ## Project Structure 6 | 7 | - **assets/**: Contains static assets such as images and icons. 8 | - `favicon.ico`: The favicon for the website. 9 | - `images/`: Directory containing example images. 10 | - **templates/**: Contains HTML templates used by the application. 11 | - `index.tmpl`: The main template for the homepage. 12 | - `foo/bar.tmpl`: The template for the "Foo" page. 13 | - **go.mod**: The Go module file, listing the dependencies required for the project. 14 | - **go.sum**: The Go checksum file, ensuring the integrity of the dependencies. 15 | - **main.go**: The main application file, setting up the web server and routes. 16 | 17 | ## Dependencies 18 | 19 | The project uses the following dependencies: 20 | 21 | - `github.com/gin-gonic/gin`: A web framework for Go. 22 | - `github.com/bytedance/sonic`: A high-performance JSON library. 23 | - `github.com/gabriel-vasile/mimetype`: A library for detecting MIME types. 24 | - And various other indirect dependencies listed in `go.mod`. 25 | 26 | ## Running the Application 27 | 28 | To run the application, use the following command: 29 | 30 | ```bash 31 | go run main.go 32 | ``` 33 | 34 | The application will start a web server on `http://localhost:8080`. You can access the following routes: 35 | 36 | - `/`: The homepage, rendered using `index.tmpl`. 37 | - `/foo`: The "Foo" page, rendered using `bar.tmpl`. 38 | - `/public/assets/images/example.png`: An example image served from the embedded assets. 39 | - `/favicon.ico`: The favicon served from the embedded assets. 40 | 41 | ## Embedding Files 42 | 43 | The `//go:embed` directive is used to embed the contents of the `assets` and `templates` directories into the Go binary. The embedded files are accessed using the `embed.FS` type. 44 | 45 | Example usage in `main.go`: 46 | 47 | ```go 48 | //go:embed assets/* templates/* 49 | var f embed.FS 50 | 51 | func main() { 52 | router := gin.Default() 53 | templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) 54 | router.SetHTMLTemplate(templ) 55 | 56 | router.StaticFS("/public", http.FS(f)) 57 | 58 | router.GET("/", func(c *gin.Context) { 59 | c.HTML(http.StatusOK, "index.tmpl", gin.H{ 60 | "title": "Main website", 61 | }) 62 | }) 63 | 64 | router.GET("/foo", func(c *gin.Context) { 65 | c.HTML(http.StatusOK, "bar.tmpl", gin.H{ 66 | "title": "Foo website", 67 | }) 68 | }) 69 | 70 | router.GET("favicon.ico", func(c *gin.Context) { 71 | file, _ := f.ReadFile("assets/favicon.ico") 72 | c.Data( 73 | http.StatusOK, 74 | "image/x-icon", 75 | file, 76 | ) 77 | }) 78 | 79 | router.Run(":8080") 80 | } 81 | ``` 82 | 83 | ## License 84 | 85 | This project is licensed under the MIT License. See the [LICENSE](../LICENSE) file for details. 86 | 87 | ## Contributing 88 | 89 | Contributions are welcome! Please see the [CONTRIBUTING](../CONTRIBUTING.md) file for guidelines. 90 | 91 | ## References 92 | 93 | - [Go 1.16 Release Notes](https://tip.golang.org/doc/go1.16#embed) 94 | - [embed package documentation](https://tip.golang.org/pkg/embed/) 95 | -------------------------------------------------------------------------------- /assets-in-binary/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gin-gonic/examples/a520ecc76386ed29686fa649d831e83cb733b015/assets-in-binary/assets/favicon.ico -------------------------------------------------------------------------------- /assets-in-binary/assets/images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gin-gonic/examples/a520ecc76386ed29686fa649d831e83cb733b015/assets-in-binary/assets/images/example.png -------------------------------------------------------------------------------- /assets-in-binary/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "html/template" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | //go:embed assets/* templates/* 12 | var f embed.FS 13 | 14 | func main() { 15 | router := gin.Default() 16 | templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) 17 | router.SetHTMLTemplate(templ) 18 | 19 | // example: /public/assets/images/example.png 20 | router.StaticFS("/public", http.FS(f)) 21 | 22 | router.GET("/", func(c *gin.Context) { 23 | c.HTML(http.StatusOK, "index.tmpl", gin.H{ 24 | "title": "Main website", 25 | }) 26 | }) 27 | 28 | router.GET("/foo", func(c *gin.Context) { 29 | c.HTML(http.StatusOK, "bar.tmpl", gin.H{ 30 | "title": "Foo website", 31 | }) 32 | }) 33 | 34 | router.GET("favicon.ico", func(c *gin.Context) { 35 | file, _ := f.ReadFile("assets/favicon.ico") 36 | c.Data( 37 | http.StatusOK, 38 | "image/x-icon", 39 | file, 40 | ) 41 | }) 42 | 43 | router.Run(":8080") 44 | } 45 | -------------------------------------------------------------------------------- /assets-in-binary/templates/foo/bar.tmpl: -------------------------------------------------------------------------------- 1 | 2 |

3 | {{ .title }} 4 |

5 | 6 | -------------------------------------------------------------------------------- /assets-in-binary/templates/index.tmpl: -------------------------------------------------------------------------------- 1 | 2 |

3 | {{ .title }} 4 |

5 | 6 | -------------------------------------------------------------------------------- /auto-tls/example1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gin-gonic/autotls" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | r := gin.Default() 12 | 13 | // Ping handler 14 | r.GET("/ping", func(c *gin.Context) { 15 | c.String(200, "pong") 16 | }) 17 | 18 | log.Fatal(autotls.Run(r, "example1.com", "example2.com")) 19 | } 20 | -------------------------------------------------------------------------------- /auto-tls/example2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gin-gonic/autotls" 7 | "github.com/gin-gonic/gin" 8 | "golang.org/x/crypto/acme/autocert" 9 | ) 10 | 11 | func main() { 12 | r := gin.Default() 13 | 14 | // Ping handler 15 | r.GET("/ping", func(c *gin.Context) { 16 | c.String(200, "pong") 17 | }) 18 | 19 | m := autocert.Manager{ 20 | Prompt: autocert.AcceptTOS, 21 | HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), 22 | Cache: autocert.DirCache("/var/www/.cache"), 23 | } 24 | 25 | log.Fatal(autotls.RunWithManager(r, &m)) 26 | } 27 | -------------------------------------------------------------------------------- /auto-tls/example3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/gin-gonic/autotls" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func main() { 15 | // Create context that listens for the interrupt signal from the OS. 16 | ctx, stop := signal.NotifyContext( 17 | context.Background(), 18 | syscall.SIGINT, 19 | syscall.SIGTERM, 20 | ) 21 | defer stop() 22 | 23 | r := gin.Default() 24 | 25 | // Ping handler 26 | r.GET("/ping", func(c *gin.Context) { 27 | c.String(http.StatusOK, "pong") 28 | }) 29 | 30 | log.Fatal(autotls.RunWithContext(ctx, r, "example1.com", "example2.com")) 31 | } 32 | -------------------------------------------------------------------------------- /basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | var db = make(map[string]string) 10 | 11 | func setupRouter() *gin.Engine { 12 | // Disable Console Color 13 | // gin.DisableConsoleColor() 14 | r := gin.Default() 15 | 16 | // Ping test 17 | r.GET("/ping", func(c *gin.Context) { 18 | c.String(http.StatusOK, "pong") 19 | }) 20 | 21 | // Get user value 22 | r.GET("/user/:name", func(c *gin.Context) { 23 | user := c.Params.ByName("name") 24 | value, ok := db[user] 25 | if ok { 26 | c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) 27 | } else { 28 | c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"}) 29 | } 30 | }) 31 | 32 | // Authorized group (uses gin.BasicAuth() middleware) 33 | // Same than: 34 | // authorized := r.Group("/") 35 | // authorized.Use(gin.BasicAuth(gin.Credentials{ 36 | // "foo": "bar", 37 | // "manu": "123", 38 | //})) 39 | authorized := r.Group("/", gin.BasicAuth(gin.Accounts{ 40 | "foo": "bar", // user:foo password:bar 41 | "manu": "123", // user:manu password:123 42 | })) 43 | 44 | /* example curl for /admin with basicauth header 45 | Zm9vOmJhcg== is base64("foo:bar") 46 | 47 | curl -X POST \ 48 | http://localhost:8080/admin \ 49 | -H 'authorization: Basic Zm9vOmJhcg==' \ 50 | -H 'content-type: application/json' \ 51 | -d '{"value":"bar"}' 52 | */ 53 | authorized.POST("admin", func(c *gin.Context) { 54 | user := c.MustGet(gin.AuthUserKey).(string) 55 | 56 | // Parse JSON 57 | var json struct { 58 | Value string `json:"value" binding:"required"` 59 | } 60 | 61 | if c.Bind(&json) == nil { 62 | db[user] = json.Value 63 | c.JSON(http.StatusOK, gin.H{"status": "ok"}) 64 | } 65 | }) 66 | 67 | return r 68 | } 69 | 70 | func main() { 71 | r := setupRouter() 72 | // Listen and Server in 0.0.0.0:8080 73 | r.Run(":8080") 74 | } 75 | -------------------------------------------------------------------------------- /basic/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPingRoute(t *testing.T) { 12 | router := setupRouter() 13 | 14 | w := httptest.NewRecorder() 15 | req, _ := http.NewRequest("GET", "/ping", nil) 16 | router.ServeHTTP(w, req) 17 | 18 | assert.Equal(t, http.StatusOK, w.Code) 19 | assert.Equal(t, "pong", w.Body.String()) 20 | } 21 | -------------------------------------------------------------------------------- /cookie/README.md: -------------------------------------------------------------------------------- 1 | # Cookie Example 2 | 3 | This example demonstrates how to set and get cookies using the Gin framework. 4 | 5 | ## Steps to Run the Example 6 | 7 | 1. **Build and Run the Server:** 8 | 9 | ```bash 10 | go run main.go 11 | ``` 12 | 13 | 1. **Login to Set the Cookie:** 14 | Open your browser and visit the login page: 15 | 16 | ```sh 17 | http://localhost:8080/login 18 | ``` 19 | 20 | 1. **Access the Home Page within 30 Seconds:** 21 | After logging in, visit the home page within 30 seconds to see the cookie in action: 22 | 23 | ```sh 24 | http://localhost:8080/home 25 | ``` 26 | 27 | 1. **Access the Home Page after 30 Seconds:** 28 | If you try to visit the home page after 30 seconds, you will see a forbidden error due to the expired cookie: 29 | 30 | ```sh 31 | http://localhost:8080/home 32 | ``` 33 | 34 | ## Code Explanation 35 | 36 | - **main.go:** 37 | - The `main.go` file contains the server setup and route definitions. 38 | - The `/login` route sets a cookie with a label "ok" and a max age of 30 seconds. 39 | - The `/home` route is protected by the `CookieTool` middleware, which checks for the presence of the cookie. 40 | 41 | ```go 42 | package main 43 | 44 | import ( 45 | "net/http" 46 | 47 | "github.com/gin-gonic/gin" 48 | ) 49 | 50 | func CookieTool() gin.HandlerFunc { 51 | return func(c *gin.Context) { 52 | // Get cookie 53 | if cookie, err := c.Cookie("label"); err == nil { 54 | if cookie == "ok" { 55 | c.Next() 56 | return 57 | } 58 | } 59 | 60 | // Cookie verification failed 61 | c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden with no cookie"}) 62 | c.Abort() 63 | } 64 | } 65 | 66 | func main() { 67 | route := gin.Default() 68 | 69 | route.GET("/login", func(c *gin.Context) { 70 | // Set cookie {"label": "ok" }, maxAge 30 seconds. 71 | c.SetCookie("label", "ok", 30, "/", "localhost", false, true) 72 | c.String(200, "Login success!") 73 | }) 74 | 75 | route.GET("/home", CookieTool(), func(c *gin.Context) { 76 | c.JSON(200, gin.H{"data": "Your home page"}) 77 | }) 78 | 79 | route.Run(":8080") 80 | } 81 | ``` 82 | 83 | ## Conclusion 84 | 85 | This example shows how to use cookies for simple session management in a Gin web application. By following the steps above, you can see how cookies are set and validated in a real-world scenario. 86 | -------------------------------------------------------------------------------- /cookie/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func CookieTool() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | // Get cookie 12 | if cookie, err := c.Cookie("label"); err == nil { 13 | if cookie == "ok" { 14 | c.Next() 15 | return 16 | } 17 | } 18 | 19 | // Cookie verification failed 20 | c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden with no cookie"}) 21 | c.Abort() 22 | } 23 | } 24 | 25 | func main() { 26 | route := gin.Default() 27 | 28 | route.GET("/login", func(c *gin.Context) { 29 | // Set cookie {"label": "ok" }, maxAge 30 seconds. 30 | c.SetCookie("label", "ok", 30, "/", "localhost", false, true) 31 | c.String(200, "Login success!") 32 | }) 33 | 34 | route.GET("/home", CookieTool(), func(c *gin.Context) { 35 | c.JSON(200, gin.H{"data": "Your home page"}) 36 | }) 37 | 38 | route.Run(":8080") 39 | } 40 | -------------------------------------------------------------------------------- /custom-validation/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/gin-gonic/gin/binding" 9 | "github.com/go-playground/validator/v10" 10 | ) 11 | 12 | // Booking contains binded and validated data. 13 | type Booking struct { 14 | CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` 15 | CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` 16 | } 17 | 18 | var bookableDate validator.Func = func(fl validator.FieldLevel) bool { 19 | date, ok := fl.Field().Interface().(time.Time) 20 | if ok { 21 | today := time.Now() 22 | if today.After(date) { 23 | return false 24 | } 25 | } 26 | return true 27 | } 28 | 29 | func main() { 30 | route := gin.Default() 31 | 32 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 33 | v.RegisterValidation("bookabledate", bookableDate) 34 | } 35 | 36 | route.GET("/bookable", getBookable) 37 | route.Run(":8085") 38 | } 39 | 40 | func getBookable(c *gin.Context) { 41 | var b Booking 42 | if err := c.ShouldBindWith(&b, binding.Query); err == nil { 43 | c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) 44 | } else { 45 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /file-binding/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "mime/multipart" 6 | "net/http" 7 | "path/filepath" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type BindFile struct { 13 | Name string `form:"name" binding:"required"` 14 | Email string `form:"email" binding:"required"` 15 | File *multipart.FileHeader `form:"file" binding:"required"` 16 | } 17 | 18 | func main() { 19 | router := gin.Default() 20 | // Set a lower memory limit for multipart forms (default is 32 MiB) 21 | router.MaxMultipartMemory = 8 << 20 // 8 MiB 22 | router.Static("/", "./public") 23 | router.POST("/upload", func(c *gin.Context) { 24 | var bindFile BindFile 25 | 26 | // Bind file 27 | if err := c.ShouldBind(&bindFile); err != nil { 28 | c.String(http.StatusBadRequest, fmt.Sprintf("err: %s", err.Error())) 29 | return 30 | } 31 | 32 | // Save uploaded file 33 | file := bindFile.File 34 | dst := filepath.Base(file.Filename) 35 | if err := c.SaveUploadedFile(file, dst); err != nil { 36 | c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) 37 | return 38 | } 39 | 40 | c.String(http.StatusOK, fmt.Sprintf("File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, bindFile.Name, bindFile.Email)) 41 | }) 42 | router.Run(":8080") 43 | } 44 | -------------------------------------------------------------------------------- /file-binding/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | File binding 6 | 7 | 8 |

Bind file with fields

9 |
10 | Name:
11 | Email:
12 | File:

13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /forward-proxy/README.md: -------------------------------------------------------------------------------- 1 | # A proxy integrate both forward and reverse 2 | 3 | Run the server and make following reqeust to test forward function. Remember to set `forward=ok` in the header so the middleware can tell which one is for forward and which one is for reverse. The demo can be adapted to puer forward proxy. 4 | 5 | ```python 6 | import requests 7 | def test_forward(): 8 | res=requests.get("http://www.baidu.com",headers={"forward":"ok"},proxies={"http":"http://127.0.0.1:8888"}) 9 | print (res.text) 10 | test_forward() 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /forward-proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/http/httputil" 7 | "net/url" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func main() { 13 | r := gin.Default() 14 | 15 | r.Use(ForwardMid) 16 | 17 | // Create a catchall route 18 | r.Any("/*proxyPath", Reverse) 19 | 20 | if err := r.Run(":8888"); err != nil { 21 | panic(err) 22 | } 23 | } 24 | 25 | func ForwardMid(c *gin.Context) { 26 | // !!! adapt to your request header set 27 | if v, ok := c.Request.Header["Forward"]; ok { 28 | if v[0] == "ok" { 29 | resp, err := http.DefaultTransport.RoundTrip(c.Request) 30 | if err != nil { 31 | http.Error(c.Writer, err.Error(), http.StatusServiceUnavailable) 32 | c.Abort() 33 | return 34 | } 35 | defer resp.Body.Close() 36 | copyHeader(c.Writer.Header(), resp.Header) 37 | c.Writer.WriteHeader(resp.StatusCode) 38 | io.Copy(c.Writer, resp.Body) 39 | c.Abort() 40 | return 41 | } 42 | } 43 | 44 | c.Next() 45 | } 46 | 47 | func copyHeader(dst, src http.Header) { 48 | for k, vv := range src { 49 | for _, v := range vv { 50 | dst.Add(k, v) 51 | } 52 | } 53 | } 54 | 55 | func Reverse(c *gin.Context) { 56 | remote, _ := url.Parse("http://xxx.xxx.xxx") 57 | proxy := httputil.NewSingleHostReverseProxy(remote) 58 | proxy.Director = func(req *http.Request) { 59 | req.Header = c.Request.Header 60 | req.Host = remote.Host 61 | req.URL.Host = remote.Host 62 | req.URL.Scheme = remote.Scheme 63 | } 64 | proxy.ServeHTTP(c.Writer, c.Request) 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gin-gonic/examples 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/fatih/color v1.18.0 7 | github.com/gin-contrib/static v1.1.5 8 | github.com/gin-gonic/autotls v1.1.2 9 | github.com/gin-gonic/gin v1.10.1 10 | github.com/go-playground/validator/v10 v10.26.0 11 | github.com/stretchr/testify v1.10.0 12 | go.uber.org/ratelimit v0.3.1 13 | golang.org/x/crypto v0.38.0 14 | golang.org/x/sync v0.14.0 15 | ) 16 | 17 | require ( 18 | github.com/benbjohnson/clock v1.3.5 // indirect 19 | github.com/bytedance/sonic v1.13.2 // indirect 20 | github.com/bytedance/sonic/loader v0.2.4 // indirect 21 | github.com/cloudwego/base64x v0.1.5 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 24 | github.com/gin-contrib/sse v1.1.0 // indirect 25 | github.com/go-playground/locales v0.14.1 // indirect 26 | github.com/go-playground/universal-translator v0.18.1 // indirect 27 | github.com/goccy/go-json v0.10.5 // indirect 28 | github.com/google/go-cmp v0.5.6 // indirect 29 | github.com/json-iterator/go v1.1.12 // indirect 30 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 31 | github.com/kr/text v0.2.0 // indirect 32 | github.com/leodido/go-urn v1.4.0 // indirect 33 | github.com/mattn/go-colorable v0.1.14 // indirect 34 | github.com/mattn/go-isatty v0.0.20 // indirect 35 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 38 | github.com/pmezard/go-difflib v1.0.0 // indirect 39 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 40 | github.com/ugorji/go/codec v1.2.12 // indirect 41 | golang.org/x/arch v0.17.0 // indirect 42 | golang.org/x/net v0.40.0 // indirect 43 | golang.org/x/sys v0.33.0 // indirect 44 | golang.org/x/text v0.25.0 // indirect 45 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 46 | google.golang.org/protobuf v1.36.6 // indirect 47 | gopkg.in/yaml.v3 v3.0.1 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= 2 | github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 4 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 5 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 6 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 7 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 8 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 9 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 10 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 11 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 16 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 17 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 18 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 19 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 20 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 21 | github.com/gin-contrib/static v1.1.5 h1:bAPqT4KTZN+4uDY1b90eSrD1t8iNzod7Jj8njwmnzz4= 22 | github.com/gin-contrib/static v1.1.5/go.mod h1:8JSEXwZHcQ0uCrLPcsvnAJ4g+ODxeupP8Zetl9fd8wM= 23 | github.com/gin-gonic/autotls v1.1.2 h1:XBGdnnLFd+bIDn70cLx8Guyie8maVNDTWmiV6jrS/JQ= 24 | github.com/gin-gonic/autotls v1.1.2/go.mod h1:JS/ZZ83HZ/VfGt2J9zOwjKm5BQ9cvUixObmIaX/xOSk= 25 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 26 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 27 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 28 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 29 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 30 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 31 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 32 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 33 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 34 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 35 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 36 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 37 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 38 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 39 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 40 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 41 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 42 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 43 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 44 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 45 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 46 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 47 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 48 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 49 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 50 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 51 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 52 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 53 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 54 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 55 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 56 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 58 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 59 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 60 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 61 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 62 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 63 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 64 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 65 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 66 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 67 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 68 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 69 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 70 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 71 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 72 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 73 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 74 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 75 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 76 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 77 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 78 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 79 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 80 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 81 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 82 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 83 | go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= 84 | go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= 85 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 86 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 87 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 88 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 89 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 90 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 91 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 92 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 93 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 94 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 95 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 96 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 97 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 98 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 99 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 100 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 101 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 102 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 103 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 104 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 105 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 106 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 107 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 108 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 109 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 110 | -------------------------------------------------------------------------------- /graceful-shutdown/close/README.md: -------------------------------------------------------------------------------- 1 | # Graceful Shutdown Example - Close Method 2 | 3 | This example demonstrates how to implement a graceful shutdown in a Gin server using the `server.Close()` method. 4 | 5 | ## Project Structure 6 | 7 | - `server.go`: The main server implementation that handles graceful shutdown. 8 | 9 | ## Usage 10 | 11 | 1. Install the required dependencies: 12 | 13 | ```bash 14 | go get -u github.com/gin-gonic/gin 15 | ``` 16 | 17 | 2. Run the server: 18 | 19 | ```bash 20 | go run server.go 21 | ``` 22 | 23 | 3. Access the server at `http://localhost:8080/`. 24 | 25 | 4. To trigger a graceful shutdown, send an interrupt signal (e.g., `Ctrl+C` in the terminal). The server will complete any ongoing requests before shutting down. 26 | 27 | ## Code Explanation 28 | 29 | - The server is initialized with a simple route that simulates a delay of 5 seconds. 30 | - A channel is created to listen for interrupt signals. 31 | - When an interrupt signal is received, the server is closed gracefully using the `server.Close()` method. 32 | -------------------------------------------------------------------------------- /graceful-shutdown/close/server.go: -------------------------------------------------------------------------------- 1 | //go:build go1.8 2 | // +build go1.8 3 | 4 | package main 5 | 6 | import ( 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "time" 12 | 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | func main() { 17 | router := gin.Default() 18 | router.GET("/", func(c *gin.Context) { 19 | time.Sleep(5 * time.Second) 20 | c.String(http.StatusOK, "Welcome Gin Server") 21 | }) 22 | 23 | server := &http.Server{ 24 | Addr: ":8080", 25 | Handler: router, 26 | } 27 | 28 | quit := make(chan os.Signal, 1) 29 | signal.Notify(quit, os.Interrupt) 30 | 31 | go func() { 32 | <-quit 33 | log.Println("receive interrupt signal") 34 | if err := server.Close(); err != nil { 35 | log.Fatal("Server Close:", err) 36 | } 37 | }() 38 | 39 | if err := server.ListenAndServe(); err != nil { 40 | if err == http.ErrServerClosed { 41 | log.Println("Server closed under request") 42 | } else { 43 | log.Fatal("Server closed unexpect") 44 | } 45 | } 46 | 47 | log.Println("Server exiting") 48 | } 49 | -------------------------------------------------------------------------------- /graceful-shutdown/graceful-shutdown/README.md: -------------------------------------------------------------------------------- 1 | # Graceful Shutdown Examples 2 | 3 | This directory contains examples demonstrating how to implement graceful shutdowns in a Gin server using context with and without context. 4 | 5 | ## Project Structure 6 | 7 | - `notify-with-context/`: Example of graceful shutdown using context. 8 | - `notify-without-context/`: Example of graceful shutdown without using context. 9 | 10 | ## Usage 11 | 12 | ### Notify with Context 13 | 14 | 1. Install the required dependencies: 15 | 16 | ```bash 17 | go get -u github.com/gin-gonic/gin 18 | ``` 19 | 20 | 2. Run the server: 21 | 22 | ```bash 23 | go run notify-with-context/server.go 24 | ``` 25 | 26 | 3. Access the server at `http://localhost:8080/`. 27 | 28 | 4. To trigger a graceful shutdown, send an interrupt signal (e.g., `Ctrl+C` in the terminal). The server will complete any ongoing requests before shutting down. 29 | 30 | ### Notify without Context 31 | 32 | 1. Install the required dependencies: 33 | 34 | ```bash 35 | go get -u github.com/gin-gonic/gin 36 | ``` 37 | 38 | 2. Run the server: 39 | 40 | ```bash 41 | go run notify-without-context/server.go 42 | ``` 43 | 44 | 3. Access the server at `http://localhost:8080/`. 45 | 46 | 4. To trigger a graceful shutdown, send an interrupt signal (e.g., `Ctrl+C` in the terminal). The server will complete any ongoing requests before shutting down. 47 | 48 | ## Code Explanation 49 | 50 | ### Notify with Context Example 51 | 52 | - The server is initialized with a simple route that simulates a delay of 10 seconds. 53 | - A context is created that listens for interrupt signals. 54 | - When an interrupt signal is received, the server is shut down gracefully using the context. 55 | 56 | ### Notify without Context Example 57 | 58 | - The server is initialized with a simple route that simulates a delay of 5 seconds. 59 | - A channel is created to listen for interrupt signals. 60 | - When an interrupt signal is received, the server is shut down gracefully using the `server.Shutdown()` method with a timeout context. 61 | -------------------------------------------------------------------------------- /graceful-shutdown/graceful-shutdown/notify-with-context/server.go: -------------------------------------------------------------------------------- 1 | // build go1.16 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "log" 8 | "net/http" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | func main() { 17 | // Create context that listens for the interrupt signal from the OS. 18 | ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) 19 | defer stop() 20 | 21 | router := gin.Default() 22 | router.GET("/", func(c *gin.Context) { 23 | time.Sleep(10 * time.Second) 24 | c.String(http.StatusOK, "Welcome Gin Server") 25 | }) 26 | 27 | srv := &http.Server{ 28 | Addr: ":8080", 29 | Handler: router, 30 | } 31 | 32 | // Initializing the server in a goroutine so that 33 | // it won't block the graceful shutdown handling below 34 | go func() { 35 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 36 | log.Fatalf("listen: %s\n", err) 37 | } 38 | }() 39 | 40 | // Listen for the interrupt signal. 41 | <-ctx.Done() 42 | 43 | // Restore default behavior on the interrupt signal and notify user of shutdown. 44 | stop() 45 | log.Println("shutting down gracefully, press Ctrl+C again to force") 46 | 47 | // The context is used to inform the server it has 5 seconds to finish 48 | // the request it is currently handling 49 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 50 | defer cancel() 51 | if err := srv.Shutdown(ctx); err != nil { 52 | log.Fatal("Server forced to shutdown: ", err) 53 | } 54 | 55 | log.Println("Server exiting") 56 | } 57 | -------------------------------------------------------------------------------- /graceful-shutdown/graceful-shutdown/notify-without-context/server.go: -------------------------------------------------------------------------------- 1 | //go:build go1.8 2 | // +build go1.8 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "log" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/gin-gonic/gin" 16 | ) 17 | 18 | func main() { 19 | router := gin.Default() 20 | router.GET("/", func(c *gin.Context) { 21 | time.Sleep(5 * time.Second) 22 | c.String(http.StatusOK, "Welcome Gin Server") 23 | }) 24 | 25 | srv := &http.Server{ 26 | Addr: ":8080", 27 | Handler: router, 28 | } 29 | 30 | // Initializing the server in a goroutine so that 31 | // it won't block the graceful shutdown handling below 32 | go func() { 33 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 34 | log.Fatalf("listen: %s\n", err) 35 | } 36 | }() 37 | 38 | // Wait for interrupt signal to gracefully shutdown the server with 39 | // a timeout of 5 seconds. 40 | quit := make(chan os.Signal, 1) 41 | // kill (no param) default send syscall.SIGTERM 42 | // kill -2 is syscall.SIGINT 43 | // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it 44 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 45 | <-quit 46 | log.Println("Shutting down server...") 47 | 48 | // The context is used to inform the server it has 5 seconds to finish 49 | // the request it is currently handling 50 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 51 | defer cancel() 52 | if err := srv.Shutdown(ctx); err != nil { 53 | log.Fatal("Server forced to shutdown: ", err) 54 | } 55 | 56 | log.Println("Server exiting") 57 | } 58 | -------------------------------------------------------------------------------- /group-routes/README.md: -------------------------------------------------------------------------------- 1 | ### Group routes 2 | 3 | This example shows how to group different routes in their own files and group them together in a orderly manner like this: 4 | 5 | ```go 6 | func getRoutes() { 7 | v1 := router.Group("/v1") 8 | addUserRoutes(v1) 9 | addPingRoutes(v1) 10 | 11 | v2 := router.Group("/v2") 12 | addPingRoutes(v2) 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /group-routes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/examples/group-routes/routes" 5 | ) 6 | 7 | func main() { 8 | // Our server will live in the routes package 9 | routes.Run() 10 | } 11 | -------------------------------------------------------------------------------- /group-routes/routes/main.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | var router = gin.Default() 8 | 9 | // Run will start the server 10 | func Run() { 11 | getRoutes() 12 | router.Run(":5000") 13 | } 14 | 15 | // getRoutes will create our routes of our entire application 16 | // this way every group of routes can be defined in their own file 17 | // so this one won't be so messy 18 | func getRoutes() { 19 | v1 := router.Group("/v1") 20 | addUserRoutes(v1) 21 | addPingRoutes(v1) 22 | 23 | v2 := router.Group("/v2") 24 | addPingRoutes(v2) 25 | } 26 | -------------------------------------------------------------------------------- /group-routes/routes/ping.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func addPingRoutes(rg *gin.RouterGroup) { 10 | ping := rg.Group("/ping") 11 | 12 | ping.GET("/", func(c *gin.Context) { 13 | c.JSON(http.StatusOK, "pong") 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /group-routes/routes/users.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func addUserRoutes(rg *gin.RouterGroup) { 10 | users := rg.Group("/users") 11 | 12 | users.GET("/", func(c *gin.Context) { 13 | c.JSON(http.StatusOK, "users") 14 | }) 15 | users.GET("/comments", func(c *gin.Context) { 16 | c.JSON(http.StatusOK, "users comments") 17 | }) 18 | users.GET("/pictures", func(c *gin.Context) { 19 | c.JSON(http.StatusOK, "users pictures") 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /grpc/example1/Makefile: -------------------------------------------------------------------------------- 1 | 2 | install: 3 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 4 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 5 | 6 | generate: clean 7 | protoc --go_out=. \ 8 | --go-grpc_out=. \ 9 | -I=$(PWD) pb/helloworld/v1/*.proto 10 | 11 | clean: 12 | rm -rf gen 13 | -------------------------------------------------------------------------------- /grpc/example1/README.md: -------------------------------------------------------------------------------- 1 | # gRPC Example 2 | 3 | This guide gets you started with gRPC in Go with a simple working example. 4 | 5 | ## Prerequisites 6 | 7 | Install the protocol compiler plugins for Go using the following commands: 8 | 9 | ```sh 10 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 11 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 12 | ``` 13 | 14 | Update your `PATH` so that the `protoc` compiler can find the plugins: 15 | 16 | ```sh 17 | export PATH="$PATH:$(go env GOPATH)/bin" 18 | ``` 19 | 20 | ## Regenerate gRPC code 21 | 22 | ```sh 23 | protoc --go_out=gen --go_opt=paths=source_relative \ 24 | --go-grpc_out=gen --go-grpc_opt=paths=source_relative \ 25 | -I=$PWD pb/helloworld.proto 26 | ``` 27 | 28 | ## Runing 29 | 30 | First Step: run grpc server 31 | 32 | ```sh 33 | go run grpc/server.go 34 | ``` 35 | 36 | Second Step: run gin server 37 | 38 | ```sh 39 | go run gin/main.go 40 | ``` 41 | 42 | ## Testing 43 | 44 | Send data to gin server: 45 | 46 | ```sh 47 | curl -v 'http://localhost:8080/rest/n/gin' 48 | ``` 49 | 50 | or using [grpcurl](https://github.com/fullstorydev/grpcurl) command: 51 | 52 | ```sh 53 | grpcurl -d '{"name": "gin"}' \ 54 | -plaintext localhost:50051 helloworld.v1.Greeter/SayHello 55 | ``` 56 | -------------------------------------------------------------------------------- /grpc/example1/gen/helloworld/v1/helloworld.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.36.5 4 | // protoc v3.20.3 5 | // source: pb/helloworld/v1/helloworld.proto 6 | 7 | package v1 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | unsafe "unsafe" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // The request message containing the user's name. 25 | type HelloRequest struct { 26 | state protoimpl.MessageState `protogen:"open.v1"` 27 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 28 | unknownFields protoimpl.UnknownFields 29 | sizeCache protoimpl.SizeCache 30 | } 31 | 32 | func (x *HelloRequest) Reset() { 33 | *x = HelloRequest{} 34 | mi := &file_pb_helloworld_v1_helloworld_proto_msgTypes[0] 35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 36 | ms.StoreMessageInfo(mi) 37 | } 38 | 39 | func (x *HelloRequest) String() string { 40 | return protoimpl.X.MessageStringOf(x) 41 | } 42 | 43 | func (*HelloRequest) ProtoMessage() {} 44 | 45 | func (x *HelloRequest) ProtoReflect() protoreflect.Message { 46 | mi := &file_pb_helloworld_v1_helloworld_proto_msgTypes[0] 47 | if x != nil { 48 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 49 | if ms.LoadMessageInfo() == nil { 50 | ms.StoreMessageInfo(mi) 51 | } 52 | return ms 53 | } 54 | return mi.MessageOf(x) 55 | } 56 | 57 | // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. 58 | func (*HelloRequest) Descriptor() ([]byte, []int) { 59 | return file_pb_helloworld_v1_helloworld_proto_rawDescGZIP(), []int{0} 60 | } 61 | 62 | func (x *HelloRequest) GetName() string { 63 | if x != nil { 64 | return x.Name 65 | } 66 | return "" 67 | } 68 | 69 | // The response message containing the greetings 70 | type HelloReply struct { 71 | state protoimpl.MessageState `protogen:"open.v1"` 72 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 73 | unknownFields protoimpl.UnknownFields 74 | sizeCache protoimpl.SizeCache 75 | } 76 | 77 | func (x *HelloReply) Reset() { 78 | *x = HelloReply{} 79 | mi := &file_pb_helloworld_v1_helloworld_proto_msgTypes[1] 80 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 81 | ms.StoreMessageInfo(mi) 82 | } 83 | 84 | func (x *HelloReply) String() string { 85 | return protoimpl.X.MessageStringOf(x) 86 | } 87 | 88 | func (*HelloReply) ProtoMessage() {} 89 | 90 | func (x *HelloReply) ProtoReflect() protoreflect.Message { 91 | mi := &file_pb_helloworld_v1_helloworld_proto_msgTypes[1] 92 | if x != nil { 93 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 94 | if ms.LoadMessageInfo() == nil { 95 | ms.StoreMessageInfo(mi) 96 | } 97 | return ms 98 | } 99 | return mi.MessageOf(x) 100 | } 101 | 102 | // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. 103 | func (*HelloReply) Descriptor() ([]byte, []int) { 104 | return file_pb_helloworld_v1_helloworld_proto_rawDescGZIP(), []int{1} 105 | } 106 | 107 | func (x *HelloReply) GetMessage() string { 108 | if x != nil { 109 | return x.Message 110 | } 111 | return "" 112 | } 113 | 114 | var File_pb_helloworld_v1_helloworld_proto protoreflect.FileDescriptor 115 | 116 | var file_pb_helloworld_v1_helloworld_proto_rawDesc = string([]byte{ 117 | 0x0a, 0x21, 0x70, 0x62, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 118 | 0x76, 0x31, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 119 | 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 120 | 0x76, 0x31, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 121 | 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 122 | 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 123 | 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 124 | 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x4f, 125 | 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x08, 0x53, 0x61, 0x79, 126 | 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x1b, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 127 | 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 128 | 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 129 | 0x76, 0x31, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 130 | 0x43, 0x0a, 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 131 | 0x6c, 0x65, 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x42, 0x0f, 132 | 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 133 | 0x01, 0x5a, 0x11, 0x67, 0x65, 0x6e, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 134 | 0x64, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 135 | }) 136 | 137 | var ( 138 | file_pb_helloworld_v1_helloworld_proto_rawDescOnce sync.Once 139 | file_pb_helloworld_v1_helloworld_proto_rawDescData []byte 140 | ) 141 | 142 | func file_pb_helloworld_v1_helloworld_proto_rawDescGZIP() []byte { 143 | file_pb_helloworld_v1_helloworld_proto_rawDescOnce.Do(func() { 144 | file_pb_helloworld_v1_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_pb_helloworld_v1_helloworld_proto_rawDesc), len(file_pb_helloworld_v1_helloworld_proto_rawDesc))) 145 | }) 146 | return file_pb_helloworld_v1_helloworld_proto_rawDescData 147 | } 148 | 149 | var file_pb_helloworld_v1_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 150 | var file_pb_helloworld_v1_helloworld_proto_goTypes = []any{ 151 | (*HelloRequest)(nil), // 0: helloworld.v1.HelloRequest 152 | (*HelloReply)(nil), // 1: helloworld.v1.HelloReply 153 | } 154 | var file_pb_helloworld_v1_helloworld_proto_depIdxs = []int32{ 155 | 0, // 0: helloworld.v1.Greeter.SayHello:input_type -> helloworld.v1.HelloRequest 156 | 1, // 1: helloworld.v1.Greeter.SayHello:output_type -> helloworld.v1.HelloReply 157 | 1, // [1:2] is the sub-list for method output_type 158 | 0, // [0:1] is the sub-list for method input_type 159 | 0, // [0:0] is the sub-list for extension type_name 160 | 0, // [0:0] is the sub-list for extension extendee 161 | 0, // [0:0] is the sub-list for field type_name 162 | } 163 | 164 | func init() { file_pb_helloworld_v1_helloworld_proto_init() } 165 | func file_pb_helloworld_v1_helloworld_proto_init() { 166 | if File_pb_helloworld_v1_helloworld_proto != nil { 167 | return 168 | } 169 | type x struct{} 170 | out := protoimpl.TypeBuilder{ 171 | File: protoimpl.DescBuilder{ 172 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 173 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_pb_helloworld_v1_helloworld_proto_rawDesc), len(file_pb_helloworld_v1_helloworld_proto_rawDesc)), 174 | NumEnums: 0, 175 | NumMessages: 2, 176 | NumExtensions: 0, 177 | NumServices: 1, 178 | }, 179 | GoTypes: file_pb_helloworld_v1_helloworld_proto_goTypes, 180 | DependencyIndexes: file_pb_helloworld_v1_helloworld_proto_depIdxs, 181 | MessageInfos: file_pb_helloworld_v1_helloworld_proto_msgTypes, 182 | }.Build() 183 | File_pb_helloworld_v1_helloworld_proto = out.File 184 | file_pb_helloworld_v1_helloworld_proto_goTypes = nil 185 | file_pb_helloworld_v1_helloworld_proto_depIdxs = nil 186 | } 187 | -------------------------------------------------------------------------------- /grpc/example1/gen/helloworld/v1/helloworld_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.5.1 4 | // - protoc v3.20.3 5 | // source: pb/helloworld/v1/helloworld.proto 6 | 7 | package v1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.64.0 or later. 19 | const _ = grpc.SupportPackageIsVersion9 20 | 21 | const ( 22 | Greeter_SayHello_FullMethodName = "/helloworld.v1.Greeter/SayHello" 23 | ) 24 | 25 | // GreeterClient is the client API for Greeter service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | // 29 | // The greeting service definition. 30 | type GreeterClient interface { 31 | // Sends a greeting 32 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) 33 | } 34 | 35 | type greeterClient struct { 36 | cc grpc.ClientConnInterface 37 | } 38 | 39 | func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { 40 | return &greeterClient{cc} 41 | } 42 | 43 | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { 44 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 45 | out := new(HelloReply) 46 | err := c.cc.Invoke(ctx, Greeter_SayHello_FullMethodName, in, out, cOpts...) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return out, nil 51 | } 52 | 53 | // GreeterServer is the server API for Greeter service. 54 | // All implementations must embed UnimplementedGreeterServer 55 | // for forward compatibility. 56 | // 57 | // The greeting service definition. 58 | type GreeterServer interface { 59 | // Sends a greeting 60 | SayHello(context.Context, *HelloRequest) (*HelloReply, error) 61 | mustEmbedUnimplementedGreeterServer() 62 | } 63 | 64 | // UnimplementedGreeterServer must be embedded to have 65 | // forward compatible implementations. 66 | // 67 | // NOTE: this should be embedded by value instead of pointer to avoid a nil 68 | // pointer dereference when methods are called. 69 | type UnimplementedGreeterServer struct{} 70 | 71 | func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { 72 | return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") 73 | } 74 | func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} 75 | func (UnimplementedGreeterServer) testEmbeddedByValue() {} 76 | 77 | // UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. 78 | // Use of this interface is not recommended, as added methods to GreeterServer will 79 | // result in compilation errors. 80 | type UnsafeGreeterServer interface { 81 | mustEmbedUnimplementedGreeterServer() 82 | } 83 | 84 | func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { 85 | // If the following call pancis, it indicates UnimplementedGreeterServer was 86 | // embedded by pointer and is nil. This will cause panics if an 87 | // unimplemented method is ever invoked, so we test this at initialization 88 | // time to prevent it from happening at runtime later due to I/O. 89 | if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 90 | t.testEmbeddedByValue() 91 | } 92 | s.RegisterService(&Greeter_ServiceDesc, srv) 93 | } 94 | 95 | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 96 | in := new(HelloRequest) 97 | if err := dec(in); err != nil { 98 | return nil, err 99 | } 100 | if interceptor == nil { 101 | return srv.(GreeterServer).SayHello(ctx, in) 102 | } 103 | info := &grpc.UnaryServerInfo{ 104 | Server: srv, 105 | FullMethod: Greeter_SayHello_FullMethodName, 106 | } 107 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 108 | return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) 109 | } 110 | return interceptor(ctx, in, info, handler) 111 | } 112 | 113 | // Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. 114 | // It's only intended for direct use with grpc.RegisterService, 115 | // and not to be introspected or modified (even as a copy) 116 | var Greeter_ServiceDesc = grpc.ServiceDesc{ 117 | ServiceName: "helloworld.v1.Greeter", 118 | HandlerType: (*GreeterServer)(nil), 119 | Methods: []grpc.MethodDesc{ 120 | { 121 | MethodName: "SayHello", 122 | Handler: _Greeter_SayHello_Handler, 123 | }, 124 | }, 125 | Streams: []grpc.StreamDesc{}, 126 | Metadata: "pb/helloworld/v1/helloworld.proto", 127 | } 128 | -------------------------------------------------------------------------------- /grpc/example1/gin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/examples/grpc/example1/gen/helloworld/v1" 9 | 10 | "github.com/gin-gonic/gin" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | func main() { 15 | // Set up a connection to the server. 16 | conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) 17 | if err != nil { 18 | log.Fatalf("did not connect: %v", err) 19 | } 20 | defer conn.Close() 21 | client := v1.NewGreeterClient(conn) 22 | 23 | // Set up a http server. 24 | r := gin.Default() 25 | r.GET("/rest/n/:name", func(c *gin.Context) { 26 | name := c.Param("name") 27 | 28 | // Contact the server and print out its response. 29 | req := &v1.HelloRequest{Name: name} 30 | res, err := client.SayHello(c, req) 31 | if err != nil { 32 | c.JSON(http.StatusInternalServerError, gin.H{ 33 | "error": err.Error(), 34 | }) 35 | return 36 | } 37 | 38 | c.JSON(http.StatusOK, gin.H{ 39 | "result": fmt.Sprint(res.Message), 40 | }) 41 | }) 42 | 43 | // Run http server 44 | if err := r.Run(":8080"); err != nil { 45 | log.Fatalf("could not run server: %v", err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /grpc/example1/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gin-gonic/examples/grpc/example1 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.1 7 | golang.org/x/net v0.40.0 8 | google.golang.org/grpc v1.72.1 9 | google.golang.org/protobuf v1.36.6 10 | ) 11 | 12 | require ( 13 | github.com/bytedance/sonic v1.13.2 // indirect 14 | github.com/bytedance/sonic/loader v0.2.4 // indirect 15 | github.com/cloudwego/base64x v0.1.5 // indirect 16 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 17 | github.com/gin-contrib/sse v1.1.0 // indirect 18 | github.com/go-playground/locales v0.14.1 // indirect 19 | github.com/go-playground/universal-translator v0.18.1 // indirect 20 | github.com/go-playground/validator/v10 v10.26.0 // indirect 21 | github.com/goccy/go-json v0.10.5 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 24 | github.com/leodido/go-urn v1.4.0 // indirect 25 | github.com/mattn/go-isatty v0.0.20 // indirect 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 27 | github.com/modern-go/reflect2 v1.0.2 // indirect 28 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 29 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 30 | github.com/ugorji/go/codec v1.2.12 // indirect 31 | golang.org/x/arch v0.17.0 // indirect 32 | golang.org/x/crypto v0.38.0 // indirect 33 | golang.org/x/sys v0.33.0 // indirect 34 | golang.org/x/text v0.25.0 // indirect 35 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /grpc/example1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 13 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 14 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 15 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 16 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 17 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 18 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 19 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 20 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 21 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 22 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 23 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 24 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 25 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 26 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 27 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 28 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 29 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 30 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 31 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 32 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 33 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 34 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 35 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 36 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 37 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 38 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 39 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 40 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 41 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 42 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 43 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 44 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 45 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 46 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 47 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 48 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 49 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 52 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 53 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 54 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 55 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 58 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 59 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 60 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 61 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 62 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 63 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 64 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 65 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 66 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 67 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 68 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 69 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 70 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 71 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 72 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 73 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 74 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 75 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 76 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 77 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 78 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 79 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 80 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 81 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 82 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 83 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 84 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 85 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 86 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 87 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 88 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 89 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 90 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 92 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 93 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 94 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 95 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= 96 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 97 | google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= 98 | google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 99 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 100 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 101 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 102 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 103 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 104 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 105 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 106 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 107 | -------------------------------------------------------------------------------- /grpc/example1/grpc/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/gin-gonic/examples/grpc/example1/gen/helloworld/v1" 8 | 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/reflection" 12 | ) 13 | 14 | // server is used to implement helloworld.GreeterServer. 15 | type server struct { 16 | v1.UnimplementedGreeterServer 17 | } 18 | 19 | // SayHello implements helloworld.GreeterServer 20 | func (s *server) SayHello(ctx context.Context, in *v1.HelloRequest) (*v1.HelloReply, error) { 21 | return &v1.HelloReply{Message: "Hello " + in.Name}, nil 22 | } 23 | 24 | func main() { 25 | lis, err := net.Listen("tcp", ":50051") 26 | if err != nil { 27 | log.Fatalf("failed to listen: %v", err) 28 | } 29 | s := grpc.NewServer() 30 | v1.RegisterGreeterServer(s, &server{}) 31 | 32 | // Register reflection service on gRPC server. 33 | reflection.Register(s) 34 | if err := s.Serve(lis); err != nil { 35 | log.Fatalf("failed to serve: %v", err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /grpc/example1/pb/helloworld/v1/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package helloworld.v1; 4 | 5 | option go_package = "gen/helloworld/v1"; 6 | 7 | option java_multiple_files = true; 8 | option java_package = "io.grpc.examples.helloworld"; 9 | option java_outer_classname = "HelloWorldProto"; 10 | 11 | // The greeting service definition. 12 | service Greeter { 13 | // Sends a greeting 14 | rpc SayHello (HelloRequest) returns (HelloReply) {} 15 | } 16 | 17 | // The request message containing the user's name. 18 | message HelloRequest { 19 | string name = 1; 20 | } 21 | 22 | // The response message containing the greetings 23 | message HelloReply { 24 | string message = 1; 25 | } 26 | -------------------------------------------------------------------------------- /http-pusher/assets/app.js: -------------------------------------------------------------------------------- 1 | console.log("http2 pusher"); 2 | -------------------------------------------------------------------------------- /http-pusher/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "log" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | var html = template.Must(template.New("https").Parse(` 11 | 12 | 13 | Https Test 14 | 15 | 16 | 17 |

Welcome, Ginner!

18 | 19 | 20 | `)) 21 | 22 | func main() { 23 | r := gin.Default() 24 | r.Static("/assets", "./assets") 25 | r.SetHTMLTemplate(html) 26 | 27 | r.GET("/", func(c *gin.Context) { 28 | if pusher := c.Writer.Pusher(); pusher != nil { 29 | // use pusher.Push() to do server push 30 | if err := pusher.Push("/assets/app.js", nil); err != nil { 31 | log.Printf("Failed to push: %v", err) 32 | } 33 | } 34 | c.HTML(200, "https", gin.H{ 35 | "status": "success", 36 | }) 37 | }) 38 | 39 | // Listen and Server in https://127.0.0.1:8080 40 | r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") 41 | } 42 | -------------------------------------------------------------------------------- /http-pusher/testdata/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla 5 | Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 6 | YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT 7 | BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 8 | +L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu 9 | g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd 10 | Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV 11 | HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau 12 | sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m 13 | oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG 14 | Dfcog5wrJytaQ6UA0wE= 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /http-pusher/testdata/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD 3 | M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf 4 | 3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY 5 | AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm 6 | V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY 7 | tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p 8 | dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q 9 | K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR 10 | 81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff 11 | DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd 12 | aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 13 | ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 14 | XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe 15 | F98XJ7tIFfJq 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /http-pusher/testdata/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET 3 | MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ 4 | dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx 5 | MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV 6 | BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 7 | ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco 8 | LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg 9 | zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd 10 | 9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw 11 | CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy 12 | em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G 13 | CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 14 | hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh 15 | y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /http2/README.md: -------------------------------------------------------------------------------- 1 | ## How to generate RSA private key and digital certificate 2 | 3 | 1. Install Openssl 4 | 5 | Please visit https://github.com/openssl/openssl to get pkg and install. 6 | 7 | 2. Generate RSA private key 8 | 9 | ```sh 10 | $ mkdir testdata 11 | $ openssl genrsa -out ./testdata/server.key 2048 12 | ``` 13 | 14 | 3. Generate digital certificate 15 | 16 | ```sh 17 | $ openssl req -new -x509 -key ./testdata/server.key -out ./testdata/server.pem -days 365 18 | ``` 19 | -------------------------------------------------------------------------------- /http2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | var html = template.Must(template.New("https").Parse(` 13 | 14 | 15 | Https Test 16 | 17 | 18 |

Welcome, Ginner!

19 | 20 | 21 | `)) 22 | 23 | func main() { 24 | logger := log.New(os.Stderr, "", 0) 25 | logger.Println("[WARNING] DON'T USE THE EMBED CERTS FROM THIS EXAMPLE IN PRODUCTION ENVIRONMENT, GENERATE YOUR OWN!") 26 | 27 | r := gin.Default() 28 | r.SetHTMLTemplate(html) 29 | 30 | r.GET("/welcome", func(c *gin.Context) { 31 | c.HTML(http.StatusOK, "https", gin.H{ 32 | "status": "success", 33 | }) 34 | }) 35 | 36 | // Listen and Server in https://127.0.0.1:8080 37 | r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") 38 | } 39 | -------------------------------------------------------------------------------- /http2/testdata/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla 5 | Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 6 | YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT 7 | BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 8 | +L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu 9 | g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd 10 | Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV 11 | HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau 12 | sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m 13 | oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG 14 | Dfcog5wrJytaQ6UA0wE= 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /http2/testdata/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD 3 | M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf 4 | 3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY 5 | AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm 6 | V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY 7 | tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p 8 | dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q 9 | K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR 10 | 81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff 11 | DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd 12 | aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 13 | ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 14 | XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe 15 | F98XJ7tIFfJq 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /http2/testdata/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET 3 | MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ 4 | dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx 5 | MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV 6 | BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 7 | ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco 8 | LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg 9 | zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd 10 | 9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw 11 | CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy 12 | em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G 13 | CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 14 | hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh 15 | y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /multiple-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "golang.org/x/sync/errgroup" 10 | ) 11 | 12 | var g errgroup.Group 13 | 14 | func router01() http.Handler { 15 | e := gin.New() 16 | e.Use(gin.Recovery()) 17 | e.GET("/", func(c *gin.Context) { 18 | c.JSON( 19 | http.StatusOK, 20 | gin.H{ 21 | "code": http.StatusOK, 22 | "error": "Welcome server 01", 23 | }, 24 | ) 25 | }) 26 | 27 | return e 28 | } 29 | 30 | func router02() http.Handler { 31 | e := gin.New() 32 | e.Use(gin.Recovery()) 33 | e.GET("/", func(c *gin.Context) { 34 | c.JSON( 35 | http.StatusOK, 36 | gin.H{ 37 | "code": http.StatusOK, 38 | "error": "Welcome server 02", 39 | }, 40 | ) 41 | }) 42 | 43 | return e 44 | } 45 | 46 | func main() { 47 | server01 := &http.Server{ 48 | Addr: ":8080", 49 | Handler: router01(), 50 | ReadTimeout: 5 * time.Second, 51 | WriteTimeout: 10 * time.Second, 52 | } 53 | 54 | server02 := &http.Server{ 55 | Addr: ":8081", 56 | Handler: router02(), 57 | ReadTimeout: 5 * time.Second, 58 | WriteTimeout: 10 * time.Second, 59 | } 60 | 61 | g.Go(func() error { 62 | return server01.ListenAndServe() 63 | }) 64 | 65 | g.Go(func() error { 66 | return server02.ListenAndServe() 67 | }) 68 | 69 | if err := g.Wait(); err != nil { 70 | log.Fatal(err) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /new_relic/README.md: -------------------------------------------------------------------------------- 1 | # New Relic Integration with Gin 2 | 3 | This example demonstrates how to integrate New Relic with a Gin web application. 4 | 5 | ## Prerequisites 6 | 7 | - Go 1.16 or later 8 | - A New Relic account and API key 9 | 10 | ## Setup 11 | 12 | 1. Clone the repository and navigate to the `new_relic` directory: 13 | 14 | ```sh 15 | git clone https://github.com/your-repo/gin-examples.git 16 | cd gin-examples/new_relic 17 | ``` 18 | 19 | 1. Install the dependencies: 20 | 21 | ```sh 22 | go mod tidy 23 | ``` 24 | 25 | 1. Set the environment variables for your New Relic application: 26 | 27 | ```sh 28 | export NEW_RELIC_APP_NAME="YourAppName" 29 | export NEW_RELIC_LICENSE_KEY="YourNewRelicLicenseKey" 30 | ``` 31 | 32 | ## Running the Application 33 | 34 | To run the application, use the following command: 35 | 36 | ```sh 37 | go run main.go 38 | ``` 39 | 40 | The application will start a web server on `http://localhost:8080`. You can access it in your browser to see the "Hello World!" message. 41 | 42 | ## Code Overview 43 | 44 | The main components of the application are: 45 | 46 | - **Gin Router**: The web framework used to handle HTTP requests. 47 | - **New Relic Application**: The New Relic agent used to monitor the application. 48 | 49 | ### main.go 50 | 51 | ```go 52 | package main 53 | 54 | import ( 55 | "log" 56 | "net/http" 57 | 58 | "github.com/gin-gonic/gin" 59 | "github.com/newrelic/go-agent/v3/integrations/nrgin" 60 | newrelic "github.com/newrelic/go-agent/v3/newrelic" 61 | ) 62 | 63 | func main() { 64 | router := gin.Default() 65 | 66 | app, err := newrelic.NewApplication( 67 | newrelic.ConfigAppName("MyApp"), 68 | newrelic.ConfigFromEnvironment(), 69 | ) 70 | if err != nil { 71 | log.Fatalf("failed to make new_relic app: %v", err) 72 | } 73 | 74 | router.Use(nrgin.Middleware(app)) 75 | router.GET("/", func(c *gin.Context) { 76 | c.String(http.StatusOK, "Hello World!\n") 77 | }) 78 | router.Run() 79 | } 80 | ``` 81 | 82 | ## License 83 | 84 | This project is licensed under the MIT License. See the [LICENSE](../LICENSE) file for details. 85 | 86 | ## Contributing 87 | 88 | Contributions are welcome! Please see the [CONTRIBUTING](../CONTRIBUTING.md) file for more information. 89 | 90 | ## Acknowledgments 91 | 92 | - [Gin Web Framework](https://github.com/gin-gonic/gin) 93 | - [New Relic Go Agent](https://github.com/newrelic/go-agent) 94 | -------------------------------------------------------------------------------- /new_relic/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.1 7 | github.com/newrelic/go-agent/v3 v3.39.0 8 | github.com/newrelic/go-agent/v3/integrations/nrgin v1.3.3 9 | ) 10 | 11 | require ( 12 | github.com/bytedance/sonic v1.13.2 // indirect 13 | github.com/bytedance/sonic/loader v0.2.4 // indirect 14 | github.com/cloudwego/base64x v0.1.5 // indirect 15 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 16 | github.com/gin-contrib/sse v1.1.0 // indirect 17 | github.com/go-playground/locales v0.14.1 // indirect 18 | github.com/go-playground/universal-translator v0.18.1 // indirect 19 | github.com/go-playground/validator/v10 v10.26.0 // indirect 20 | github.com/goccy/go-json v0.10.5 // indirect 21 | github.com/json-iterator/go v1.1.12 // indirect 22 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 23 | github.com/leodido/go-urn v1.4.0 // indirect 24 | github.com/mattn/go-isatty v0.0.20 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 29 | github.com/ugorji/go/codec v1.2.12 // indirect 30 | golang.org/x/arch v0.17.0 // indirect 31 | golang.org/x/crypto v0.38.0 // indirect 32 | golang.org/x/net v0.40.0 // indirect 33 | golang.org/x/sys v0.33.0 // indirect 34 | golang.org/x/text v0.25.0 // indirect 35 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 36 | google.golang.org/grpc v1.72.1 // indirect 37 | google.golang.org/protobuf v1.36.6 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /new_relic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/newrelic/go-agent/v3/integrations/nrgin" 10 | newrelic "github.com/newrelic/go-agent/v3/newrelic" 11 | ) 12 | 13 | func main() { 14 | router := gin.Default() 15 | 16 | // cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY")) 17 | app, err := newrelic.NewApplication( 18 | newrelic.ConfigAppName("MyApp"), 19 | newrelic.ConfigFromEnvironment(), 20 | newrelic.ConfigDebugLogger(os.Stdout), 21 | newrelic.ConfigAppLogForwardingEnabled(true), 22 | newrelic.ConfigCodeLevelMetricsEnabled(true), 23 | newrelic.ConfigCodeLevelMetricsPathPrefixes("go-agent/v3"), 24 | ) 25 | if err != nil { 26 | log.Fatalf("failed to make new_relic app: %v", err) 27 | } 28 | 29 | router.Use(nrgin.Middleware(app)) 30 | router.GET("/", func(c *gin.Context) { 31 | c.String(http.StatusOK, "Hello World!\n") 32 | }) 33 | router.Run() 34 | } 35 | -------------------------------------------------------------------------------- /oidc/README.md: -------------------------------------------------------------------------------- 1 | # OIDC GitHub OAuth Example 2 | 3 | This project demonstrates how to implement GitHub OAuth2 authentication using the Gin web framework in Go. It includes routes for logging in with GitHub, handling the OAuth2 callback, and displaying user information. 4 | 5 | ## Table of Contents 6 | 7 | - [OIDC GitHub OAuth Example](#oidc-github-oauth-example) 8 | - [Table of Contents](#table-of-contents) 9 | - [Overview](#overview) 10 | - [Setup](#setup) 11 | - [Prerequisites](#prerequisites) 12 | - [Installation](#installation) 13 | - [Clone the repository](#clone-the-repository) 14 | - [Install dependencies](#install-dependencies) 15 | - [Set up environment variables](#set-up-environment-variables) 16 | - [Run the application](#run-the-application) 17 | - [Usage](#usage) 18 | - [Routes](#routes) 19 | - [Example](#example) 20 | - [Code Explanation](#code-explanation) 21 | - [Main Components](#main-components) 22 | - [Main Routes](#main-routes) 23 | - [OAuth2 Configuration](#oauth2-configuration) 24 | - [State Management](#state-management) 25 | - [User Information](#user-information) 26 | - [License](#license) 27 | 28 | ## Overview 29 | 30 | This project provides a simple example of how to integrate GitHub OAuth2 authentication into a Go web application using the Gin framework. It includes the following features: 31 | 32 | - Redirecting users to GitHub for authentication 33 | - Handling the OAuth2 callback 34 | - Retrieving and displaying user information from GitHub 35 | - Protecting routes that require authentication 36 | 37 | ## Setup 38 | 39 | ### Prerequisites 40 | 41 | - Go 1.23.1 or later 42 | - GitHub OAuth2 application credentials (Client ID and Client Secret) 43 | 44 | ### Installation 45 | 46 | #### Clone the repository 47 | 48 | ```sh 49 | git clone https://github.com/yourusername/oidc.git 50 | cd oidc 51 | ``` 52 | 53 | #### Install dependencies 54 | 55 | ```sh 56 | go mod tidy 57 | ``` 58 | 59 | #### Set up environment variables 60 | 61 | ```sh 62 | export GITHUB_CLIENT_ID=your_github_client_id 63 | export GITHUB_CLIENT_SECRET=your_github_client_secret 64 | ``` 65 | 66 | #### Run the application 67 | 68 | ```sh 69 | go run main.go 70 | ``` 71 | 72 | 1. Open your browser and navigate to `http://localhost:8080`. 73 | 74 | ## Usage 75 | 76 | ### Routes 77 | 78 | - `/`: Home page with a link to log in with GitHub. 79 | - `/login`: Redirects to GitHub for authentication. 80 | - `/callback`: Handles the GitHub OAuth2 callback and displays user information. 81 | - `/protected`: A protected route that requires authentication. 82 | 83 | ### Example 84 | 85 | 1. Visit the home page at `http://localhost:8080`. 86 | 2. Click the "Log in with GitHub" link to be redirected to GitHub for authentication. 87 | 3. After logging in, you will be redirected back to the application and your GitHub user information will be displayed. 88 | 4. Access the protected route at `http://localhost:8080/protected`. 89 | 90 | ## Code Explanation 91 | 92 | ### Main Components 93 | 94 | - `main.go`: The main application file containing the routes and OAuth2 configuration. 95 | - `templates/`: Directory containing HTML templates for the home and success pages. 96 | 97 | ### Main Routes 98 | 99 | - **Home Route (`/`)**: Displays the home page with a link to log in with GitHub. 100 | 101 | ```go 102 | r.GET("/", func(c *gin.Context) { 103 | c.HTML(http.StatusOK, "index.html", gin.H{ 104 | "title": "GitHub OAuth Example", 105 | }) 106 | }) 107 | ``` 108 | 109 | - **Login Route (`/login`)**: Redirects to GitHub for authentication. 110 | 111 | ```go 112 | r.GET("/login", func(c *gin.Context) { 113 | state, err := generateRandomState() 114 | if err != nil { 115 | c.String(http.StatusInternalServerError, "Unable to generate state value") 116 | return 117 | } 118 | stateCache.Set(state, true, cache.DefaultExpiration) 119 | authURL := oauth2Config.AuthCodeURL(state) 120 | c.Redirect(http.StatusFound, authURL) 121 | }) 122 | ``` 123 | 124 | - **Callback Route (`/callback`)**: Handles the GitHub OAuth2 callback and displays user information. 125 | 126 | ```go 127 | r.GET("/callback", func(c *gin.Context) { 128 | state := c.Query("state") 129 | if _, exists := stateCache.Get(state); !exists { 130 | c.String(http.StatusBadRequest, "Invalid state value") 131 | return 132 | } 133 | stateCache.Delete(state) 134 | code := c.Query("code") 135 | if code == "" { 136 | c.String(http.StatusBadRequest, "Authorization code not provided") 137 | return 138 | } 139 | token, err := oauth2Config.Exchange(context.Background(), code) 140 | if err != nil { 141 | c.String(http.StatusInternalServerError, "Unable to exchange access token: "+err.Error()) 142 | return 143 | } 144 | client := oauth2Config.Client(context.Background(), token) 145 | resp, err := client.Get(userInfoURL) 146 | if err != nil { 147 | c.String(http.StatusInternalServerError, "Unable to retrieve user information: "+err.Error()) 148 | return 149 | } 150 | defer resp.Body.Close() 151 | var user GitHubUser 152 | if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { 153 | c.String(http.StatusInternalServerError, "Unable to parse user information: "+err.Error()) 154 | return 155 | } 156 | c.HTML(http.StatusOK, "success.html", gin.H{ 157 | "title": "Authentication Successful", 158 | "username": user.Login, 159 | "name": user.Name, 160 | "email": user.Email, 161 | "avatar_url": user.AvatarURL, 162 | }) 163 | }) 164 | ``` 165 | 166 | - **Protected Route (`/protected`)**: A protected route that requires authentication. 167 | 168 | ```go 169 | r.GET("/protected", func(c *gin.Context) { 170 | c.String(http.StatusOK, "This is a protected resource!") 171 | }) 172 | ``` 173 | 174 | ### OAuth2 Configuration 175 | 176 | The OAuth2 configuration is set up using the `oauth2.Config` struct, which includes the client ID, client secret, redirect URL, and GitHub OAuth2 endpoint. 177 | 178 | ```go 179 | var oauth2Config = oauth2.Config{ 180 | ClientID: clientID, 181 | ClientSecret: clientSecret, 182 | RedirectURL: redirectURL, 183 | Endpoint: github.Endpoint, 184 | Scopes: []string{"user:email", "read:user"}, 185 | } 186 | ``` 187 | 188 | ### State Management 189 | 190 | A random state value is generated and stored in a cache to prevent CSRF attacks during the OAuth2 authentication process. 191 | 192 | ```go 193 | func generateRandomState() (string, error) { 194 | b := make([]byte, 32) 195 | if _, err := rand.Read(b); err != nil { 196 | return "", err 197 | } 198 | return base64.URLEncoding.EncodeToString(b), nil 199 | } 200 | ``` 201 | 202 | ### User Information 203 | 204 | The user information is retrieved from GitHub using the access token obtained during the OAuth2 authentication process. 205 | 206 | ```go 207 | type GitHubUser struct { 208 | ID int `json:"id"` 209 | Login string `json:"login"` 210 | Name string `json:"name"` 211 | Email string `json:"email"` 212 | AvatarURL string `json:"avatar_url"` 213 | } 214 | ``` 215 | 216 | ## License 217 | 218 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 219 | -------------------------------------------------------------------------------- /oidc/go.mod: -------------------------------------------------------------------------------- 1 | module oidc 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.1 7 | github.com/patrickmn/go-cache v2.1.0+incompatible 8 | golang.org/x/oauth2 v0.30.0 9 | ) 10 | 11 | require ( 12 | github.com/bytedance/sonic v1.13.2 // indirect 13 | github.com/bytedance/sonic/loader v0.2.4 // indirect 14 | github.com/cloudwego/base64x v0.1.5 // indirect 15 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 16 | github.com/gin-contrib/sse v1.1.0 // indirect 17 | github.com/go-playground/locales v0.14.1 // indirect 18 | github.com/go-playground/universal-translator v0.18.1 // indirect 19 | github.com/go-playground/validator/v10 v10.26.0 // indirect 20 | github.com/goccy/go-json v0.10.5 // indirect 21 | github.com/google/go-cmp v0.6.0 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 24 | github.com/leodido/go-urn v1.4.0 // indirect 25 | github.com/mattn/go-isatty v0.0.20 // indirect 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 27 | github.com/modern-go/reflect2 v1.0.2 // indirect 28 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 29 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 30 | github.com/ugorji/go/codec v1.2.12 // indirect 31 | golang.org/x/arch v0.17.0 // indirect 32 | golang.org/x/crypto v0.38.0 // indirect 33 | golang.org/x/net v0.40.0 // indirect 34 | golang.org/x/sys v0.33.0 // indirect 35 | golang.org/x/text v0.25.0 // indirect 36 | google.golang.org/protobuf v1.36.6 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /oidc/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 13 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 14 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 15 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 16 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 17 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 20 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 21 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 22 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 23 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 24 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 25 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 26 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 27 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 28 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 29 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 32 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 33 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 34 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 35 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 36 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 37 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 38 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 39 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 40 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 41 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 44 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 45 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 46 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 47 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 48 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 49 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 53 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 54 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 55 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 56 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 57 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 58 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 59 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 60 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 61 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 62 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 63 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 64 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 65 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 66 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 67 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 68 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 69 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 70 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 71 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 72 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 73 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 74 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 76 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 77 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 78 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 79 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 80 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 83 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 84 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 85 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 87 | -------------------------------------------------------------------------------- /oidc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "embed" 7 | "encoding/base64" 8 | "encoding/json" 9 | "html/template" 10 | "log" 11 | "net/http" 12 | "os" 13 | "time" 14 | 15 | "github.com/gin-gonic/gin" 16 | "github.com/patrickmn/go-cache" 17 | "golang.org/x/oauth2" 18 | "golang.org/x/oauth2/github" 19 | ) 20 | 21 | var ( 22 | clientID = os.Getenv("GITHUB_CLIENT_ID") 23 | clientSecret = os.Getenv("GITHUB_CLIENT_SECRET") 24 | redirectURL = "http://localhost:8080/callback" 25 | ) 26 | 27 | var ( 28 | // Cache for storing state 29 | stateCache = cache.New(10*time.Minute, 20*time.Minute) 30 | 31 | // OAuth2 configuration 32 | oauth2Config = oauth2.Config{ 33 | ClientID: clientID, 34 | ClientSecret: clientSecret, 35 | RedirectURL: redirectURL, 36 | Endpoint: github.Endpoint, 37 | Scopes: []string{"user:email", "read:user"}, 38 | } 39 | 40 | // GitHub user information API 41 | userInfoURL = "https://api.github.com/user" 42 | ) 43 | 44 | func generateRandomState() (string, error) { 45 | b := make([]byte, 32) 46 | if _, err := rand.Read(b); err != nil { 47 | return "", err 48 | } 49 | return base64.URLEncoding.EncodeToString(b), nil 50 | } 51 | 52 | type GitHubUser struct { 53 | ID int `json:"id"` 54 | Login string `json:"login"` 55 | Name string `json:"name"` 56 | Email string `json:"email"` 57 | AvatarURL string `json:"avatar_url"` 58 | } 59 | 60 | //go:embed templates/* 61 | var templatesFS embed.FS 62 | 63 | func main() { 64 | r := gin.Default() 65 | 66 | // Set HTML templates 67 | r.SetHTMLTemplate(template.Must(template.ParseFS(templatesFS, "templates/*"))) 68 | 69 | // Home route 70 | r.GET("/", func(c *gin.Context) { 71 | c.HTML(http.StatusOK, "index.html", gin.H{ 72 | "title": "GitHub OAuth Example", 73 | }) 74 | }) 75 | 76 | // Login route - Redirect to GitHub for authentication 77 | r.GET("/login", func(c *gin.Context) { 78 | state, err := generateRandomState() 79 | if err != nil { 80 | c.String(http.StatusInternalServerError, "Unable to generate state value") 81 | return 82 | } 83 | 84 | // Store state for later verification 85 | stateCache.Set(state, true, cache.DefaultExpiration) 86 | 87 | // Redirect to GitHub for authentication 88 | authURL := oauth2Config.AuthCodeURL(state) 89 | c.Redirect(http.StatusFound, authURL) 90 | }) 91 | 92 | // Callback route - Handle GitHub authentication callback 93 | r.GET("/callback", func(c *gin.Context) { 94 | // Retrieve and verify state 95 | state := c.Query("state") 96 | if _, exists := stateCache.Get(state); !exists { 97 | c.String(http.StatusBadRequest, "Invalid state value") 98 | return 99 | } 100 | stateCache.Delete(state) 101 | 102 | // Retrieve code 103 | code := c.Query("code") 104 | if code == "" { 105 | c.String(http.StatusBadRequest, "Authorization code not provided") 106 | return 107 | } 108 | 109 | // Exchange code for access token 110 | token, err := oauth2Config.Exchange(context.Background(), code) 111 | if err != nil { 112 | c.String(http.StatusInternalServerError, "Unable to exchange access token: "+err.Error()) 113 | return 114 | } 115 | 116 | // Use access token to get user information 117 | client := oauth2Config.Client(context.Background(), token) 118 | resp, err := client.Get(userInfoURL) 119 | if err != nil { 120 | c.String(http.StatusInternalServerError, "Unable to retrieve user information: "+err.Error()) 121 | return 122 | } 123 | defer resp.Body.Close() 124 | 125 | var user GitHubUser 126 | if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { 127 | c.String(http.StatusInternalServerError, "Unable to parse user information: "+err.Error()) 128 | return 129 | } 130 | 131 | // Display user information on success page 132 | c.HTML(http.StatusOK, "success.html", gin.H{ 133 | "title": "Authentication Successful", 134 | "username": user.Login, 135 | "name": user.Name, 136 | "email": user.Email, 137 | "avatar_url": user.AvatarURL, 138 | }) 139 | }) 140 | 141 | // Protected route - Requires authentication to access 142 | r.GET("/protected", func(c *gin.Context) { 143 | // In a real application, session or JWT token should be checked here 144 | // This is just a simplified example 145 | c.String(http.StatusOK, "This is a protected resource!") 146 | }) 147 | 148 | // Start server 149 | log.Println("Server running at http://localhost:8080") 150 | r.Run(":8080") 151 | } 152 | -------------------------------------------------------------------------------- /oidc/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ .title }} 6 | 30 | 31 | 32 | 33 |

{{ .title }}

34 |

Click the button below to log in with your GitHub account

35 | 36 | Log in with GitHub 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /oidc/templates/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ .title }} 6 | 28 | 29 | 30 | 31 |

{{ .title }}

32 |
33 | User Avatar 34 |

{{ .name }}

35 |

Username: {{ .username }}

36 |

Email: {{ .email }}

37 |
38 |

Return to Home

39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /otel/README.md: -------------------------------------------------------------------------------- 1 | # OTEL Integration with Gin 2 | 3 | This project demonstrates how to integrate OpenTelemetry (OTEL) with the Gin web framework in Go. It provides a minimal example to get you started with tracing HTTP requests in a Gin application using OTEL. 4 | 5 | ## Prerequisites 6 | 7 | - Go 1.23 or later 8 | - Git 9 | 10 | ## Installation 11 | 12 | ### Clone the repository 13 | 14 | ```bash 15 | git clone https://github.com/your-repo/otel-gin-example.git 16 | cd otel-gin-example 17 | ``` 18 | 19 | ### Install the dependencies 20 | 21 | ```bash 22 | go mod tidy 23 | ``` 24 | 25 | ## Running the Example 26 | 27 | To run the example application, use the following command: 28 | 29 | ```bash 30 | go run main.go 31 | ``` 32 | 33 | This will start the Gin server on `http://localhost:8080`. 34 | 35 | ## Example Output 36 | 37 | When you run the example, you should see output similar to the following in the terminal: 38 | 39 | ```bash 40 | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. 41 | 42 | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. 43 | - using env: export GIN_MODE=release 44 | - using code: gin.SetMode(gin.ReleaseMode) 45 | 46 | [GIN-debug] GET / --> main.main.func1 (4 handlers) 47 | [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. 48 | Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details. 49 | [GIN-debug] Listening and serving HTTP on :8080 50 | traceID: 00477d7b56b757d0581328ef21d17271; spanID: 9d05e83c0c188a16; isSampled: true 51 | [GIN] 2024/10/15 - 11:44:32 | 200 | 31.209µs | ::1 | GET "/" 52 | ``` 53 | 54 | ## Project Structure 55 | 56 | - `main.go`: The main application file that sets up the Gin server and OTEL middleware. 57 | - `go.mod`: The Go module file that lists the project dependencies. 58 | - `go.sum`: The Go checksum file for dependencies. 59 | 60 | ## Dependencies 61 | 62 | This project uses the following dependencies: 63 | 64 | - [Gin](https://github.com/gin-gonic/gin) v1.10.0: A web framework written in Go. 65 | - [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-go) v1.35.0: A set of APIs, libraries, agents, and instrumentation to provide observability. 66 | - [OTEL Gin Middleware](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/gin-gonic/gin/otelgin) v0.60.0: Middleware for integrating OTEL with Gin. 67 | 68 | ## Configuration 69 | 70 | The example sets `ContextWithFallback = true` to use OTEL in Gin. This configuration is necessary for the example to work but may not be ideal for production use. Adjust the configuration as needed for your use case. 71 | 72 | ## License 73 | 74 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 75 | 76 | ## Contributing 77 | 78 | Contributions are welcome! Please see the [CONTRIBUTING](CONTRIBUTING.md) file for more information. 79 | 80 | ## Acknowledgments 81 | 82 | - [Gin](https://github.com/gin-gonic/gin) 83 | - [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-go) 84 | - [OTEL Gin Middleware](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/gin-gonic/gin/otelgin) 85 | -------------------------------------------------------------------------------- /otel/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.1 7 | go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 8 | go.opentelemetry.io/otel v1.35.0 9 | go.opentelemetry.io/otel/sdk v1.35.0 10 | go.opentelemetry.io/otel/trace v1.35.0 11 | ) 12 | 13 | require ( 14 | github.com/bytedance/sonic v1.13.2 // indirect 15 | github.com/bytedance/sonic/loader v0.2.4 // indirect 16 | github.com/cloudwego/base64x v0.1.5 // indirect 17 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 18 | github.com/gin-contrib/sse v1.1.0 // indirect 19 | github.com/go-logr/logr v1.4.2 // indirect 20 | github.com/go-logr/stdr v1.2.2 // indirect 21 | github.com/go-playground/locales v0.14.1 // indirect 22 | github.com/go-playground/universal-translator v0.18.1 // indirect 23 | github.com/go-playground/validator/v10 v10.26.0 // indirect 24 | github.com/goccy/go-json v0.10.5 // indirect 25 | github.com/google/uuid v1.6.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 28 | github.com/leodido/go-urn v1.4.0 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 31 | github.com/modern-go/reflect2 v1.0.2 // indirect 32 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 33 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 34 | github.com/ugorji/go/codec v1.2.12 // indirect 35 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 36 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 37 | golang.org/x/arch v0.17.0 // indirect 38 | golang.org/x/crypto v0.38.0 // indirect 39 | golang.org/x/net v0.40.0 // indirect 40 | golang.org/x/sys v0.33.0 // indirect 41 | golang.org/x/text v0.25.0 // indirect 42 | google.golang.org/protobuf v1.36.6 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /otel/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 13 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 14 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 15 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 16 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 17 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 18 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 19 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 20 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 21 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 22 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 23 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 24 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 25 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 26 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 27 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 28 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 29 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 30 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 31 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 32 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 33 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 34 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 36 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 37 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 38 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 39 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 40 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 41 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 42 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 43 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 44 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 45 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 46 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 47 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 48 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 49 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 50 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 51 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 52 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 54 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 55 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 56 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 57 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 58 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 59 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 62 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 63 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 64 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 65 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 66 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 67 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 68 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 69 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 70 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 71 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 72 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 73 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 74 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 75 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 76 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 77 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 78 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 79 | go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 h1:jj/B7eX95/mOxim9g9laNZkOHKz/XCHG0G410SntRy4= 80 | go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0/go.mod h1:ZvRTVaYYGypytG0zRp2A60lpj//cMq3ZnxYdZaljVBM= 81 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 82 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 83 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 84 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 85 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 86 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 87 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 88 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 89 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 90 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 91 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 92 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 93 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 94 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 95 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 96 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 97 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 99 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 100 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 101 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 102 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 103 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 104 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 105 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 106 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 107 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 108 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 109 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 110 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 111 | -------------------------------------------------------------------------------- /otel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/gin-gonic/gin" 10 | 11 | "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" 12 | "go.opentelemetry.io/otel" 13 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 14 | "go.opentelemetry.io/otel/trace" 15 | ) 16 | 17 | var applicationName string = "demo" 18 | 19 | func main() { 20 | otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.AlwaysSample())))) 21 | app := gin.Default() 22 | app.ContextWithFallback = true 23 | app.Use(otelgin.Middleware(applicationName)) 24 | 25 | app.GET("/", func(ctx *gin.Context) { 26 | traceID, spanID, isSampled := GetTraceInfo(ctx) 27 | fmt.Printf("traceID: %v; spanID: %v; isSampled: %v\n", traceID, spanID, isSampled) 28 | }) 29 | 30 | go func() { 31 | if err := app.Run(":8080"); err != nil { 32 | log.Fatalln(err) 33 | } 34 | }() 35 | 36 | http.Get("http://localhost:8080/") 37 | } 38 | 39 | func GetTraceInfo(ctx context.Context) (traceID string, spanID string, isSampled bool) { 40 | spanCtx := trace.SpanContextFromContext(ctx) 41 | 42 | if spanCtx.HasTraceID() { 43 | traceID = spanCtx.TraceID().String() 44 | } 45 | if spanCtx.HasSpanID() { 46 | spanID = spanCtx.SpanID().String() 47 | } 48 | 49 | isSampled = spanCtx.IsSampled() 50 | 51 | return traceID, spanID, isSampled 52 | } 53 | -------------------------------------------------------------------------------- /ratelimiter/README.md: -------------------------------------------------------------------------------- 1 | # Ratelimit in Gin 2 | 3 | This project is a sample for ratelimit using Leaky Bucket. Although the golang official pkg provide a implement with Token Bucket [time/rate](https://pkg.go.dev/golang.org/x/time/rate?tab=doc), 4 | 5 | you can also make your owns via gin's functional `Use()` to integrate extra middlewares. 6 | 7 | ## Effect 8 | 9 | ```go 10 | // You can assign the ratelimit of the server 11 | // rps: requests per second 12 | go run rate.go -rps=100 13 | ``` 14 | 15 | - Let's hava a simple test by ab with 3000 mock requests, not surprisingly,it will takes 10ms each request. 16 | 17 | ```bash 18 | ab -n 3000 -c 1 http://localhost:8080/rate 19 | ``` 20 | 21 | - Gin Log Output 22 | 23 | ```bash 24 | [GIN] 10ms 25 | [GIN] 2020/07/14 - 15:07:49 | 200 | 8.307734ms | 127.0.0.1 | GET /rate 26 | [GIN] 10ms 27 | [GIN] 2020/07/14 - 15:07:49 | 200 | 10.512913ms | 127.0.0.1 | GET /rate 28 | [GIN] 10ms 29 | [GIN] 2020/07/14 - 15:07:49 | 200 | 8.54681ms | 127.0.0.1 | GET /rate 30 | [GIN] 10ms 31 | [GIN] 2020/07/14 - 15:07:49 | 200 | 8.356436ms | 127.0.0.1 | GET /rate 32 | [GIN] 10ms 33 | [GIN] 2020/07/14 - 15:07:49 | 200 | 9.677276ms | 127.0.0.1 | GET /rate 34 | [GIN] 10ms 35 | [GIN] 2020/07/14 - 15:07:49 | 200 | 7.536156ms | 127.0.0.1 | GET /rate 36 | [GIN] 10ms 37 | [GIN] 2020/07/14 - 15:07:49 | 200 | 11.57084ms | 127.0.0.1 | GET /rate 38 | [GIN] 10ms 39 | [GIN] 2020/07/14 - 15:07:49 | 200 | 7.802ms | 127.0.0.1 | GET /rate 40 | [GIN] 10ms 41 | [GIN] 2020/07/14 - 15:07:49 | 200 | 9.602394ms | 127.0.0.1 | GET /rate 42 | ``` 43 | 44 | - AB Test Reporter 45 | 46 | ```java 47 | Concurrency Level: 1 48 | Time taken for tests: 30.00 seconds 49 | Complete requests: 3000 50 | Requests per second: 100.00 [#/sec] (mean) 51 | Time per request: 10.001 [ms] (mean) 52 | Time per request: 10.001 [ms] (mean, across all concurrent requests) 53 | ``` 54 | -------------------------------------------------------------------------------- /ratelimiter/rate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "time" 7 | 8 | "github.com/fatih/color" 9 | "github.com/gin-gonic/gin" 10 | "go.uber.org/ratelimit" 11 | ) 12 | 13 | var ( 14 | limit ratelimit.Limiter 15 | rps = flag.Int("rps", 100, "request per second") 16 | ) 17 | 18 | func init() { 19 | log.SetFlags(0) 20 | log.SetPrefix("[GIN] ") 21 | log.SetOutput(gin.DefaultWriter) 22 | } 23 | 24 | func leakBucket() gin.HandlerFunc { 25 | prev := time.Now() 26 | return func(ctx *gin.Context) { 27 | now := limit.Take() 28 | log.Print(color.CyanString("%v", now.Sub(prev))) 29 | prev = now 30 | } 31 | } 32 | 33 | func ginRun(rps int) { 34 | limit = ratelimit.New(rps) 35 | 36 | app := gin.Default() 37 | app.Use(leakBucket()) 38 | 39 | app.GET("/rate", func(ctx *gin.Context) { 40 | ctx.JSON(200, "rate limiting test") 41 | }) 42 | 43 | log.Printf(color.CyanString("Current Rate Limit: %v requests/s", rps)) 44 | app.Run(":8080") 45 | } 46 | 47 | func main() { 48 | flag.Parse() 49 | ginRun(*rps) 50 | } 51 | -------------------------------------------------------------------------------- /realtime-advanced/Makefile: -------------------------------------------------------------------------------- 1 | all: deps build 2 | 3 | .PHONY: deps 4 | deps: 5 | go get -d -v github.com/dustin/go-broadcast/... 6 | go get -d -v github.com/manucorporat/stats/... 7 | 8 | .PHONY: build 9 | build: deps 10 | go build -o realtime-advanced main.go rooms.go routes.go stats.go 11 | -------------------------------------------------------------------------------- /realtime-advanced/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91 7 | github.com/gin-gonic/gin v1.10.1 8 | github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 9 | ) 10 | 11 | require ( 12 | github.com/bytedance/sonic v1.13.2 // indirect 13 | github.com/bytedance/sonic/loader v0.2.4 // indirect 14 | github.com/cloudwego/base64x v0.1.5 // indirect 15 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 16 | github.com/gin-contrib/sse v1.1.0 // indirect 17 | github.com/go-playground/locales v0.14.1 // indirect 18 | github.com/go-playground/universal-translator v0.18.1 // indirect 19 | github.com/go-playground/validator/v10 v10.26.0 // indirect 20 | github.com/goccy/go-json v0.10.5 // indirect 21 | github.com/json-iterator/go v1.1.12 // indirect 22 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 23 | github.com/leodido/go-urn v1.4.0 // indirect 24 | github.com/mattn/go-isatty v0.0.20 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 29 | github.com/ugorji/go/codec v1.2.12 // indirect 30 | golang.org/x/arch v0.17.0 // indirect 31 | golang.org/x/crypto v0.38.0 // indirect 32 | golang.org/x/net v0.40.0 // indirect 33 | golang.org/x/sys v0.33.0 // indirect 34 | golang.org/x/text v0.25.0 // indirect 35 | google.golang.org/protobuf v1.36.6 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /realtime-advanced/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91 h1:jAUM3D1KIrJmwx60DKB+a/qqM69yHnu6otDGVa2t0vs= 13 | github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91/go.mod h1:8rK6Kbo1Jd6sK22b24aPVgAm3jlNy1q1ft+lBALdIqA= 14 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 15 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 16 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 17 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 18 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 19 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 20 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 21 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 22 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 23 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 24 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 25 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 26 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 27 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 28 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 29 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 30 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 31 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 33 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 34 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 35 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 36 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 37 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 38 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 39 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 40 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 41 | github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= 42 | github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 48 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 49 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 50 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 51 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 52 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 54 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 55 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 56 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 57 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 58 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 59 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 60 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 61 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 62 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 63 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 64 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 65 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 66 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 67 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 68 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 69 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 70 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 71 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 72 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 73 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 74 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 76 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 77 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 78 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 79 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 81 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 82 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 87 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 88 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 89 | -------------------------------------------------------------------------------- /realtime-advanced/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | ConfigRuntime() 12 | StartWorkers() 13 | StartGin() 14 | } 15 | 16 | // ConfigRuntime sets the number of operating system threads. 17 | func ConfigRuntime() { 18 | nuCPU := runtime.NumCPU() 19 | runtime.GOMAXPROCS(nuCPU) 20 | fmt.Printf("Running with %d CPUs\n", nuCPU) 21 | } 22 | 23 | // StartWorkers start starsWorker by goroutine. 24 | func StartWorkers() { 25 | go statsWorker() 26 | } 27 | 28 | // StartGin starts gin web server with setting router. 29 | func StartGin() { 30 | gin.SetMode(gin.ReleaseMode) 31 | 32 | router := gin.New() 33 | router.Use(rateLimit, gin.Recovery()) 34 | router.LoadHTMLGlob("resources/*.templ.html") 35 | router.Static("/static", "resources/static") 36 | router.GET("/", index) 37 | router.GET("/room/:roomid", roomGET) 38 | router.POST("/room-post/:roomid", roomPOST) 39 | router.GET("/stream/:roomid", streamRoom) 40 | 41 | router.Run(":80") 42 | } 43 | -------------------------------------------------------------------------------- /realtime-advanced/resources/static/prismjs.min.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+go */ 2 | /** 3 | * prism.js default theme for JavaScript, CSS and HTML 4 | * Based on dabblet (http://dabblet.com) 5 | * @author Lea Verou 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: black; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | } 29 | 30 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 31 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 32 | text-shadow: none; 33 | background: #b3d4fc; 34 | } 35 | 36 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 37 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 38 | text-shadow: none; 39 | background: #b3d4fc; 40 | } 41 | 42 | @media print { 43 | code[class*="language-"], 44 | pre[class*="language-"] { 45 | text-shadow: none; 46 | } 47 | } 48 | 49 | /* Code blocks */ 50 | pre[class*="language-"] { 51 | padding: 1em; 52 | margin: .5em 0; 53 | overflow: auto; 54 | } 55 | 56 | :not(pre) > code[class*="language-"], 57 | pre[class*="language-"] { 58 | background: #f5f2f0; 59 | } 60 | 61 | /* Inline code */ 62 | :not(pre) > code[class*="language-"] { 63 | padding: .1em; 64 | border-radius: .3em; 65 | } 66 | 67 | .token.comment, 68 | .token.prolog, 69 | .token.doctype, 70 | .token.cdata { 71 | color: slategray; 72 | } 73 | 74 | .token.punctuation { 75 | color: #999; 76 | } 77 | 78 | .namespace { 79 | opacity: .7; 80 | } 81 | 82 | .token.property, 83 | .token.tag, 84 | .token.boolean, 85 | .token.number, 86 | .token.constant, 87 | .token.symbol, 88 | .token.deleted { 89 | color: #905; 90 | } 91 | 92 | .token.selector, 93 | .token.attr-name, 94 | .token.string, 95 | .token.char, 96 | .token.builtin, 97 | .token.inserted { 98 | color: #690; 99 | } 100 | 101 | .token.operator, 102 | .token.entity, 103 | .token.url, 104 | .language-css .token.string, 105 | .style .token.string { 106 | color: #a67f59; 107 | background: hsla(0, 0%, 100%, .5); 108 | } 109 | 110 | .token.atrule, 111 | .token.attr-value, 112 | .token.keyword { 113 | color: #07a; 114 | } 115 | 116 | .token.function { 117 | color: #DD4A68; 118 | } 119 | 120 | .token.regex, 121 | .token.important, 122 | .token.variable { 123 | color: #e90; 124 | } 125 | 126 | .token.important, 127 | .token.bold { 128 | font-weight: bold; 129 | } 130 | .token.italic { 131 | font-style: italic; 132 | } 133 | 134 | .token.entity { 135 | cursor: help; 136 | } 137 | 138 | -------------------------------------------------------------------------------- /realtime-advanced/resources/static/prismjs.min.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+go */ 2 | self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{};var 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(/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),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}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,i=0;r=a[i++];)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 i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var s="";for(var o in i.attributes)s+=o+'="'+(i.attributes[o]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+s+">"+i.content+""},!self.document)return self.addEventListener?(self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),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);; 3 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/("|')(\\\n|\\?.)*?\1/,"class-name":{pattern:/((?:(?: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":{pattern:/[a-z0-9_]+\(/i,inside:{punctuation:/\(/}},number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|~|\^|%/,ignore:/&(lt|gt|amp);/i,punctuation:/[{}[\];(),.:]/};; 4 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|get|if|implements|import|in|instanceof|interface|let|new|null|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]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|-?Infinity)\b/,"function":/(?!\d)[a-z0-9_$]+(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/i,inside:{tag:{pattern:/|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}});; 5 | 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"];; 6 | -------------------------------------------------------------------------------- /realtime-advanced/resources/static/realtime.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function StartRealtime(roomid, timestamp) { 4 | StartEpoch(timestamp); 5 | StartSSE(roomid); 6 | StartForm(); 7 | } 8 | 9 | function StartForm() { 10 | $('#chat-message').focus(); 11 | $('#chat-form').ajaxForm(function() { 12 | $('#chat-message').val(''); 13 | $('#chat-message').focus(); 14 | }); 15 | } 16 | 17 | function StartEpoch(timestamp) { 18 | var windowSize = 60; 19 | var height = 200; 20 | var defaultData = histogram(windowSize, timestamp); 21 | 22 | window.heapChart = $('#heapChart').epoch({ 23 | type: 'time.area', 24 | axes: ['bottom', 'left'], 25 | height: height, 26 | historySize: 10, 27 | data: [ 28 | {values: defaultData}, 29 | {values: defaultData} 30 | ] 31 | }); 32 | 33 | window.mallocsChart = $('#mallocsChart').epoch({ 34 | type: 'time.area', 35 | axes: ['bottom', 'left'], 36 | height: height, 37 | historySize: 10, 38 | data: [ 39 | {values: defaultData}, 40 | {values: defaultData} 41 | ] 42 | }); 43 | 44 | window.messagesChart = $('#messagesChart').epoch({ 45 | type: 'time.line', 46 | axes: ['bottom', 'left'], 47 | height: 240, 48 | historySize: 10, 49 | data: [ 50 | {values: defaultData}, 51 | {values: defaultData}, 52 | {values: defaultData} 53 | ] 54 | }); 55 | } 56 | 57 | function StartSSE(roomid) { 58 | if (!window.EventSource) { 59 | alert("EventSource is not enabled in this browser"); 60 | return; 61 | } 62 | var source = new EventSource('/stream/'+roomid); 63 | source.addEventListener('message', newChatMessage, false); 64 | source.addEventListener('stats', stats, false); 65 | } 66 | 67 | function stats(e) { 68 | var data = parseJSONStats(e.data); 69 | heapChart.push(data.heap); 70 | mallocsChart.push(data.mallocs); 71 | messagesChart.push(data.messages); 72 | } 73 | 74 | function parseJSONStats(e) { 75 | var data = jQuery.parseJSON(e); 76 | var timestamp = data.timestamp; 77 | 78 | var heap = [ 79 | {time: timestamp, y: data.HeapInuse}, 80 | {time: timestamp, y: data.StackInuse} 81 | ]; 82 | 83 | var mallocs = [ 84 | {time: timestamp, y: data.Mallocs}, 85 | {time: timestamp, y: data.Frees} 86 | ]; 87 | var messages = [ 88 | {time: timestamp, y: data.Connected}, 89 | {time: timestamp, y: data.Inbound}, 90 | {time: timestamp, y: data.Outbound} 91 | ]; 92 | 93 | return { 94 | heap: heap, 95 | mallocs: mallocs, 96 | messages: messages 97 | } 98 | } 99 | 100 | function newChatMessage(e) { 101 | var data = jQuery.parseJSON(e.data); 102 | var nick = data.nick; 103 | var message = data.message; 104 | var style = rowStyle(nick); 105 | var html = ""+nick+""+message+""; 106 | $('#chat').append(html); 107 | 108 | $("#chat-scroll").scrollTop($("#chat-scroll")[0].scrollHeight); 109 | } 110 | 111 | function histogram(windowSize, timestamp) { 112 | var entries = new Array(windowSize); 113 | for(var i = 0; i < windowSize; i++) { 114 | entries[i] = {time: (timestamp-windowSize+i-1), y:0}; 115 | } 116 | return entries; 117 | } 118 | 119 | var entityMap = { 120 | "&": "&", 121 | "<": "<", 122 | ">": ">", 123 | '"': '"', 124 | "'": ''', 125 | "/": '/' 126 | }; 127 | 128 | function rowStyle(nick) { 129 | var classes = ['active', 'success', 'info', 'warning', 'danger']; 130 | var index = hashCode(nick)%5; 131 | return classes[index]; 132 | } 133 | 134 | function hashCode(s){ 135 | return Math.abs(s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)); 136 | } 137 | 138 | function escapeHtml(string) { 139 | return String(string).replace(/[&<>"'\/]/g, function (s) { 140 | return entityMap[s]; 141 | }); 142 | } 143 | 144 | window.StartRealtime = StartRealtime 145 | -------------------------------------------------------------------------------- /realtime-advanced/rooms.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/dustin/go-broadcast" 4 | 5 | var roomChannels = make(map[string]broadcast.Broadcaster) 6 | 7 | func openListener(roomid string) chan interface{} { 8 | listener := make(chan interface{}) 9 | room(roomid).Register(listener) 10 | return listener 11 | } 12 | 13 | func closeListener(roomid string, listener chan interface{}) { 14 | room(roomid).Unregister(listener) 15 | close(listener) 16 | } 17 | 18 | func room(roomid string) broadcast.Broadcaster { 19 | b, ok := roomChannels[roomid] 20 | if !ok { 21 | b = broadcast.NewBroadcaster(10) 22 | roomChannels[roomid] = b 23 | } 24 | return b 25 | } 26 | -------------------------------------------------------------------------------- /realtime-advanced/routes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func rateLimit(c *gin.Context) { 15 | ip := c.ClientIP() 16 | value := int(ips.Add(ip, 1)) 17 | if value%50 == 0 { 18 | fmt.Printf("ip: %s, count: %d\n", ip, value) 19 | } 20 | if value >= 200 { 21 | if value%200 == 0 { 22 | fmt.Println("ip blocked") 23 | } 24 | c.String(http.StatusServiceUnavailable, "you were automatically banned :)") 25 | c.Abort() 26 | return 27 | } 28 | } 29 | 30 | func index(c *gin.Context) { 31 | c.Redirect(http.StatusMovedPermanently, "/room/hn") 32 | } 33 | 34 | func roomGET(c *gin.Context) { 35 | roomid := c.Param("roomid") 36 | nick := c.Query("nick") 37 | if len(nick) < 2 { 38 | nick = "" 39 | } 40 | if len(nick) > 13 { 41 | nick = nick[0:12] + "..." 42 | } 43 | c.HTML(http.StatusOK, "room_login.templ.html", gin.H{ 44 | "roomid": roomid, 45 | "nick": nick, 46 | "timestamp": time.Now().Unix(), 47 | }) 48 | } 49 | 50 | func roomPOST(c *gin.Context) { 51 | roomid := c.Param("roomid") 52 | nick := c.Query("nick") 53 | message := c.PostForm("message") 54 | message = strings.TrimSpace(message) 55 | 56 | validMessage := len(message) > 1 && len(message) < 200 57 | validNick := len(nick) > 1 && len(nick) < 14 58 | if !validMessage || !validNick { 59 | c.JSON(http.StatusBadRequest, gin.H{ 60 | "status": "failed", 61 | "error": "the message or nickname is too long", 62 | }) 63 | return 64 | } 65 | 66 | post := gin.H{ 67 | "nick": html.EscapeString(nick), 68 | "message": html.EscapeString(message), 69 | } 70 | messages.Add("inbound", 1) 71 | room(roomid).Submit(post) 72 | c.JSON(http.StatusOK, post) 73 | } 74 | 75 | func streamRoom(c *gin.Context) { 76 | roomid := c.Param("roomid") 77 | listener := openListener(roomid) 78 | ticker := time.NewTicker(1 * time.Second) 79 | users.Add("connected", 1) 80 | defer func() { 81 | closeListener(roomid, listener) 82 | ticker.Stop() 83 | users.Add("disconnected", 1) 84 | }() 85 | 86 | c.Stream(func(w io.Writer) bool { 87 | select { 88 | case msg := <-listener: 89 | messages.Add("outbound", 1) 90 | c.SSEvent("message", msg) 91 | case <-ticker.C: 92 | c.SSEvent("stats", Stats()) 93 | } 94 | return true 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /realtime-advanced/stats.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | 8 | "github.com/manucorporat/stats" 9 | ) 10 | 11 | var ( 12 | ips = stats.New() 13 | messages = stats.New() 14 | users = stats.New() 15 | mutexStats sync.RWMutex 16 | savedStats map[string]uint64 17 | ) 18 | 19 | func statsWorker() { 20 | c := time.Tick(1 * time.Second) 21 | var lastMallocs uint64 22 | var lastFrees uint64 23 | for range c { 24 | var stats runtime.MemStats 25 | runtime.ReadMemStats(&stats) 26 | 27 | mutexStats.Lock() 28 | savedStats = map[string]uint64{ 29 | "timestamp": uint64(time.Now().Unix()), 30 | "HeapInuse": stats.HeapInuse, 31 | "StackInuse": stats.StackInuse, 32 | "Mallocs": stats.Mallocs - lastMallocs, 33 | "Frees": stats.Frees - lastFrees, 34 | "Inbound": uint64(messages.Get("inbound")), 35 | "Outbound": uint64(messages.Get("outbound")), 36 | "Connected": connectedUsers(), 37 | } 38 | lastMallocs = stats.Mallocs 39 | lastFrees = stats.Frees 40 | messages.Reset() 41 | mutexStats.Unlock() 42 | } 43 | } 44 | 45 | func connectedUsers() uint64 { 46 | connected := users.Get("connected") - users.Get("disconnected") 47 | if connected < 0 { 48 | return 0 49 | } 50 | return uint64(connected) 51 | } 52 | 53 | // Stats returns savedStats data. 54 | func Stats() map[string]uint64 { 55 | mutexStats.RLock() 56 | defer mutexStats.RUnlock() 57 | 58 | return savedStats 59 | } 60 | -------------------------------------------------------------------------------- /realtime-chat/Makefile: -------------------------------------------------------------------------------- 1 | all: deps build 2 | 3 | .PHONY: deps 4 | deps: 5 | go get -d -v github.com/dustin/go-broadcast/... 6 | 7 | .PHONY: build 8 | build: deps 9 | go build -o realtime-chat main.go rooms.go template.go 10 | -------------------------------------------------------------------------------- /realtime-chat/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91 7 | github.com/gin-gonic/gin v1.10.1 8 | ) 9 | 10 | require ( 11 | github.com/bytedance/sonic v1.13.2 // indirect 12 | github.com/bytedance/sonic/loader v0.2.4 // indirect 13 | github.com/cloudwego/base64x v0.1.5 // indirect 14 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 15 | github.com/gin-contrib/sse v1.1.0 // indirect 16 | github.com/go-playground/locales v0.14.1 // indirect 17 | github.com/go-playground/universal-translator v0.18.1 // indirect 18 | github.com/go-playground/validator/v10 v10.26.0 // indirect 19 | github.com/goccy/go-json v0.10.5 // indirect 20 | github.com/json-iterator/go v1.1.12 // indirect 21 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 22 | github.com/leodido/go-urn v1.4.0 // indirect 23 | github.com/mattn/go-isatty v0.0.20 // indirect 24 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 25 | github.com/modern-go/reflect2 v1.0.2 // indirect 26 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 27 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 28 | github.com/ugorji/go/codec v1.2.12 // indirect 29 | golang.org/x/arch v0.17.0 // indirect 30 | golang.org/x/crypto v0.38.0 // indirect 31 | golang.org/x/net v0.40.0 // indirect 32 | golang.org/x/sys v0.33.0 // indirect 33 | golang.org/x/text v0.25.0 // indirect 34 | google.golang.org/protobuf v1.36.6 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /realtime-chat/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91 h1:jAUM3D1KIrJmwx60DKB+a/qqM69yHnu6otDGVa2t0vs= 13 | github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91/go.mod h1:8rK6Kbo1Jd6sK22b24aPVgAm3jlNy1q1ft+lBALdIqA= 14 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 15 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 16 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 17 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 18 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 19 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 20 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 21 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 22 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 23 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 24 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 25 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 26 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 27 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 28 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 29 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 30 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 31 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 33 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 34 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 35 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 36 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 37 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 38 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 39 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 40 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 41 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 42 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 43 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 46 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 47 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 48 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 49 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 53 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 54 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 55 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 56 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 57 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 58 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 59 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 60 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 61 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 62 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 63 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 64 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 65 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 66 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 67 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 68 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 69 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 70 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 71 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 72 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 74 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 75 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 76 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 77 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 78 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 79 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 80 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 83 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 84 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 85 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 87 | -------------------------------------------------------------------------------- /realtime-chat/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "math/rand" 7 | "net/http" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | var roomManager *Manager 13 | 14 | func main() { 15 | roomManager = NewRoomManager() 16 | router := gin.Default() 17 | router.SetHTMLTemplate(html) 18 | 19 | router.GET("/room/:roomid", roomGET) 20 | router.POST("/room/:roomid", roomPOST) 21 | router.DELETE("/room/:roomid", roomDELETE) 22 | router.GET("/stream/:roomid", stream) 23 | 24 | router.Run(":8080") 25 | } 26 | 27 | func stream(c *gin.Context) { 28 | roomid := c.Param("roomid") 29 | listener := roomManager.OpenListener(roomid) 30 | defer roomManager.CloseListener(roomid, listener) 31 | 32 | clientGone := c.Request.Context().Done() 33 | c.Stream(func(w io.Writer) bool { 34 | select { 35 | case <-clientGone: 36 | return false 37 | case message := <-listener: 38 | c.SSEvent("message", message) 39 | return true 40 | } 41 | }) 42 | } 43 | 44 | func roomGET(c *gin.Context) { 45 | roomid := c.Param("roomid") 46 | userid := fmt.Sprint(rand.Int31()) 47 | c.HTML(http.StatusOK, "chat_room", gin.H{ 48 | "roomid": roomid, 49 | "userid": userid, 50 | }) 51 | } 52 | 53 | func roomPOST(c *gin.Context) { 54 | roomid := c.Param("roomid") 55 | userid := c.PostForm("user") 56 | message := c.PostForm("message") 57 | roomManager.Submit(userid, roomid, message) 58 | 59 | c.JSON(http.StatusOK, gin.H{ 60 | "status": "success", 61 | "message": message, 62 | }) 63 | } 64 | 65 | func roomDELETE(c *gin.Context) { 66 | roomid := c.Param("roomid") 67 | roomManager.DeleteBroadcast(roomid) 68 | } 69 | -------------------------------------------------------------------------------- /realtime-chat/rooms.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/dustin/go-broadcast" 5 | ) 6 | 7 | type Message struct { 8 | UserId string 9 | RoomId string 10 | Text string 11 | } 12 | 13 | type Listener struct { 14 | RoomId string 15 | Chan chan interface{} 16 | } 17 | 18 | type Manager struct { 19 | roomChannels map[string]broadcast.Broadcaster 20 | open chan *Listener 21 | close chan *Listener 22 | delete chan string 23 | messages chan *Message 24 | } 25 | 26 | func NewRoomManager() *Manager { 27 | manager := &Manager{ 28 | roomChannels: make(map[string]broadcast.Broadcaster), 29 | open: make(chan *Listener, 100), 30 | close: make(chan *Listener, 100), 31 | delete: make(chan string, 100), 32 | messages: make(chan *Message, 100), 33 | } 34 | 35 | go manager.run() 36 | return manager 37 | } 38 | 39 | func (m *Manager) run() { 40 | for { 41 | select { 42 | case listener := <-m.open: 43 | m.register(listener) 44 | case listener := <-m.close: 45 | m.deregister(listener) 46 | case roomid := <-m.delete: 47 | m.deleteBroadcast(roomid) 48 | case message := <-m.messages: 49 | m.room(message.RoomId).Submit(message.UserId + ": " + message.Text) 50 | } 51 | } 52 | } 53 | 54 | func (m *Manager) register(listener *Listener) { 55 | m.room(listener.RoomId).Register(listener.Chan) 56 | } 57 | 58 | func (m *Manager) deregister(listener *Listener) { 59 | m.room(listener.RoomId).Unregister(listener.Chan) 60 | close(listener.Chan) 61 | } 62 | 63 | func (m *Manager) deleteBroadcast(roomid string) { 64 | b, ok := m.roomChannels[roomid] 65 | if ok { 66 | b.Close() 67 | delete(m.roomChannels, roomid) 68 | } 69 | } 70 | 71 | func (m *Manager) room(roomid string) broadcast.Broadcaster { 72 | b, ok := m.roomChannels[roomid] 73 | if !ok { 74 | b = broadcast.NewBroadcaster(10) 75 | m.roomChannels[roomid] = b 76 | } 77 | return b 78 | } 79 | 80 | func (m *Manager) OpenListener(roomid string) chan interface{} { 81 | listener := make(chan interface{}) 82 | m.open <- &Listener{ 83 | RoomId: roomid, 84 | Chan: listener, 85 | } 86 | return listener 87 | } 88 | 89 | func (m *Manager) CloseListener(roomid string, channel chan interface{}) { 90 | m.close <- &Listener{ 91 | RoomId: roomid, 92 | Chan: channel, 93 | } 94 | } 95 | 96 | func (m *Manager) DeleteBroadcast(roomid string) { 97 | m.delete <- roomid 98 | } 99 | 100 | func (m *Manager) Submit(userid, roomid, text string) { 101 | msg := &Message{ 102 | UserId: userid, 103 | RoomId: roomid, 104 | Text: text, 105 | } 106 | m.messages <- msg 107 | } 108 | -------------------------------------------------------------------------------- /realtime-chat/template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "html/template" 4 | 5 | var html = template.Must(template.New("chat_room").Parse(` 6 | 7 | 8 | {{.roomid}} 9 | 10 | 11 | 12 | 33 | 34 | 35 |

Welcome to {{.roomid}} room

36 |
37 |
38 | User: 39 | Message: 40 | 41 |
42 | 43 | 44 | `)) 45 | -------------------------------------------------------------------------------- /reverse-proxy/README.md: -------------------------------------------------------------------------------- 1 | # A simple reverse proxy 2 | 3 | We can see real server in real_server.go and proxy server in reverse_server.go 4 | 5 | Run this two file and if we do some request like `curl 'http://localhost:2002/something` 6 | 7 | we will get a response in JSON contains ip of client and path we requested. 8 | -------------------------------------------------------------------------------- /reverse-proxy/realServer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | const ( 11 | Addr = "127.0.0.1:2003" 12 | ) 13 | 14 | func main() { 15 | r := gin.Default() 16 | r.GET("/:path", func(c *gin.Context) { 17 | // in this handler, we just simply send some basic info back to proxy response. 18 | req := c.Request 19 | urlPath := fmt.Sprintf("http://%s%s", Addr, req.URL.Path) 20 | realIP := fmt.Sprintf("RemoteAddr=%s,X-Forwarded-For=%v,X-Real-Ip=%v", req.RemoteAddr, req.Header.Get("X-Forwarded-For"), req.Header.Get("X-Real-Ip")) 21 | c.JSON(200, gin.H{ 22 | "path": urlPath, 23 | "ip": realIP, 24 | }) 25 | }) 26 | 27 | if err := r.Run(Addr); err != nil { 28 | log.Printf("Error: %v", err) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /reverse-proxy/reverseServer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | const ( 13 | // this is our reverse server ip address 14 | ReverseServerAddr = "127.0.0.1:2002" 15 | ) 16 | 17 | // maybe we can have many real server addresses and do some load balanced strategy. 18 | var RealAddr = []string{ 19 | "http://127.0.0.1:2003", 20 | } 21 | 22 | // a fake function that we can do strategy here. 23 | func getLoadBalanceAddr() string { 24 | return RealAddr[0] 25 | } 26 | 27 | func main() { 28 | r := gin.Default() 29 | r.GET("/:path", func(c *gin.Context) { 30 | // step 1: resolve proxy address, change scheme and host in requets 31 | req := c.Request 32 | proxy, err := url.Parse(getLoadBalanceAddr()) 33 | if err != nil { 34 | log.Printf("error in parse addr: %v", err) 35 | c.String(500, "error") 36 | return 37 | } 38 | req.URL.Scheme = proxy.Scheme 39 | req.URL.Host = proxy.Host 40 | 41 | // step 2: use http.Transport to do request to real server. 42 | transport := http.DefaultTransport 43 | resp, err := transport.RoundTrip(req) 44 | if err != nil { 45 | log.Printf("error in roundtrip: %v", err) 46 | c.String(500, "error") 47 | return 48 | } 49 | 50 | // step 3: return real server response to upstream. 51 | for k, vv := range resp.Header { 52 | for _, v := range vv { 53 | c.Header(k, v) 54 | } 55 | } 56 | defer resp.Body.Close() 57 | bufio.NewReader(resp.Body).WriteTo(c.Writer) 58 | return 59 | }) 60 | 61 | if err := r.Run(ReverseServerAddr); err != nil { 62 | log.Printf("Error: %v", err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /secure-web-app/README.md: -------------------------------------------------------------------------------- 1 | # Example of a secure web application with Gin 2 | 3 | This is an example of a secure web application with Gin. It includes the following security headers: 4 | 5 | - Content-Security-Policy 6 | - Permissions-Policy 7 | - Referrer-Policy 8 | - Strict-Transport-Security 9 | - X-Frame-Options 10 | - X-Xss-Protection 11 | - X-Content-Type-Options 12 | 13 | Also the web application has strict Host Header to avoid SSRF and Host Header Injection. 14 | 15 | 1. Security Headers Example 16 | 17 | ``` 18 | curl http://localhost:8080 -I 19 | 20 | HTTP/1.1 404 Not Found 21 | Content-Security-Policy: default-src 'self'; connect-src *; font-src *; script-src-elem * 'unsafe-inline'; img-src * data:; style-src * 'unsafe-inline'; 22 | Content-Type: text/plain 23 | Permissions-Policy: geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=() 24 | Referrer-Policy: strict-origin 25 | Strict-Transport-Security: max-age=31536000; includeSubDomains; preload 26 | X-Content-Type-Options: nosniff 27 | X-Frame-Options: DENY 28 | X-Xss-Protection: 1; mode=block 29 | Date: Thu, 28 Mar 2024 11:38:05 GMT 30 | Content-Length: 18 31 | ``` 32 | 33 | 2. Host Header Injection Example 34 | ``` 35 | curl http://localhost:8080 -I -H "Host:neti.ee" 36 | 37 | HTTP/1.1 400 Bad Request 38 | Content-Type: application/json; charset=utf-8 39 | Date: Thu, 28 Mar 2024 11:38:23 GMT 40 | Content-Length: 31 41 | ``` -------------------------------------------------------------------------------- /secure-web-app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "time" 8 | 9 | "github.com/gin-contrib/static" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func NewRouter() *gin.Engine { 14 | // Set the router as the default one shipped with Gin 15 | router := gin.Default() 16 | expectedHost := "localhost:8080" 17 | 18 | // Setup Security Headers 19 | router.Use(func(c *gin.Context) { 20 | if c.Request.Host != expectedHost { 21 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid host header"}) 22 | return 23 | } 24 | c.Header("X-Frame-Options", "DENY") 25 | c.Header("Content-Security-Policy", "default-src 'self'; connect-src *; font-src *; script-src-elem * 'unsafe-inline'; img-src * data:; style-src * 'unsafe-inline';") 26 | c.Header("X-XSS-Protection", "1; mode=block") 27 | c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") 28 | c.Header("Referrer-Policy", "strict-origin") 29 | c.Header("X-Content-Type-Options", "nosniff") 30 | c.Header("Permissions-Policy", "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()") 31 | c.Next() 32 | }) 33 | 34 | // Serve frontend static files 35 | router.Use(static.Serve("/", static.LocalFile("./public/", true))) 36 | 37 | // Setup route group for the API 38 | api := router.Group("/api") 39 | { 40 | apiHandler := func(c *gin.Context) { 41 | c.JSON(http.StatusOK, gin.H{ 42 | "message": "Uniform API", 43 | }) 44 | } 45 | api.GET("", apiHandler) 46 | api.GET("/", apiHandler) 47 | } 48 | 49 | return router 50 | } 51 | 52 | func main() { 53 | 54 | httpPort := os.Getenv("API_PORT") 55 | if httpPort == "" { 56 | httpPort = "8080" 57 | } 58 | 59 | // Initialize router 60 | router := NewRouter() 61 | 62 | // Create server with timeout 63 | srv := &http.Server{ 64 | Addr: ":" + httpPort, 65 | Handler: router, 66 | // set timeout due CWE-400 - Potential Slowloris Attack 67 | ReadHeaderTimeout: 5 * time.Second, 68 | } 69 | 70 | if err := srv.ListenAndServe(); err != nil { 71 | log.Printf("Failed to start server: %v", err) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /send_chunked_data/send_chunked_data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func main() { 13 | log.SetFlags(log.LstdFlags | log.Lshortfile) 14 | r := gin.Default() 15 | r.GET("/test_stream", func(c *gin.Context) { 16 | w := c.Writer 17 | header := w.Header() 18 | header.Set("Transfer-Encoding", "chunked") 19 | header.Set("Content-Type", "text/html") 20 | w.WriteHeader(http.StatusOK) 21 | w.Write([]byte(` 22 | 23 | 24 | `)) 25 | w.(http.Flusher).Flush() 26 | for i := 0; i < 10; i++ { 27 | w.Write([]byte(fmt.Sprintf(` 28 |

%d

29 | `, i))) 30 | w.(http.Flusher).Flush() 31 | time.Sleep(time.Duration(1) * time.Second) 32 | } 33 | w.Write([]byte(` 34 | 35 | 36 | 37 | `)) 38 | w.(http.Flusher).Flush() 39 | }) 40 | 41 | r.Run("127.0.0.1:8080") 42 | } 43 | 44 | /* 45 | browser test url: 46 | http://127.0.0.1:8080/test_stream 47 | */ 48 | -------------------------------------------------------------------------------- /server-sent-event/README.md: -------------------------------------------------------------------------------- 1 | # Server Sent Event 2 | 3 | It is Way to send events from server to clients. 4 | 5 | Here is Example for SSE with basic authentication using gin. 6 | 7 | ## How to Run? 8 | 9 | 1. Run `go run main.go` 10 | 11 | 2. Open `http://127.0.0.1:8085` in browser and authorize with username and password given in main.go and see messages. 12 | -------------------------------------------------------------------------------- /server-sent-event/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // It keeps a list of clients those are currently attached 13 | // and broadcasting events to those clients. 14 | type Event struct { 15 | // Events are pushed to this channel by the main events-gathering routine 16 | Message chan string 17 | 18 | // New client connections 19 | NewClients chan chan string 20 | 21 | // Closed client connections 22 | ClosedClients chan chan string 23 | 24 | // Total client connections 25 | TotalClients map[chan string]bool 26 | } 27 | 28 | // New event messages are broadcast to all registered client connection channels 29 | type ClientChan chan string 30 | 31 | func main() { 32 | router := gin.Default() 33 | 34 | // Initialize new streaming server 35 | stream := NewServer() 36 | 37 | // We are streaming current time to clients in the interval 10 seconds 38 | go func() { 39 | for { 40 | time.Sleep(time.Second * 10) 41 | now := time.Now().Format("2006-01-02 15:04:05") 42 | currentTime := fmt.Sprintf("The Current Time Is %v", now) 43 | 44 | // Send current time to clients message channel 45 | stream.Message <- currentTime 46 | } 47 | }() 48 | 49 | // Basic Authentication 50 | authorized := router.Group("/", gin.BasicAuth(gin.Accounts{ 51 | "admin": "admin123", // username : admin, password : admin123 52 | })) 53 | 54 | // Authorized client can stream the event 55 | // Add event-streaming headers 56 | authorized.GET("/stream", HeadersMiddleware(), stream.serveHTTP(), func(c *gin.Context) { 57 | v, ok := c.Get("clientChan") 58 | if !ok { 59 | return 60 | } 61 | clientChan, ok := v.(ClientChan) 62 | if !ok { 63 | return 64 | } 65 | c.Stream(func(w io.Writer) bool { 66 | // Stream message to client from message channel 67 | if msg, ok := <-clientChan; ok { 68 | c.SSEvent("message", msg) 69 | return true 70 | } 71 | return false 72 | }) 73 | }) 74 | 75 | // Parse Static files 76 | router.StaticFile("/", "./public/index.html") 77 | 78 | router.Run(":8085") 79 | } 80 | 81 | // Initialize event and Start procnteessing requests 82 | func NewServer() (event *Event) { 83 | event = &Event{ 84 | Message: make(chan string), 85 | NewClients: make(chan chan string), 86 | ClosedClients: make(chan chan string), 87 | TotalClients: make(map[chan string]bool), 88 | } 89 | 90 | go event.listen() 91 | 92 | return 93 | } 94 | 95 | // It Listens all incoming requests from clients. 96 | // Handles addition and removal of clients and broadcast messages to clients. 97 | func (stream *Event) listen() { 98 | for { 99 | select { 100 | // Add new available client 101 | case client := <-stream.NewClients: 102 | stream.TotalClients[client] = true 103 | log.Printf("Client added. %d registered clients", len(stream.TotalClients)) 104 | 105 | // Remove closed client 106 | case client := <-stream.ClosedClients: 107 | delete(stream.TotalClients, client) 108 | close(client) 109 | log.Printf("Removed client. %d registered clients", len(stream.TotalClients)) 110 | 111 | // Broadcast message to client 112 | case eventMsg := <-stream.Message: 113 | for clientMessageChan := range stream.TotalClients { 114 | select { 115 | case clientMessageChan <- eventMsg: 116 | // Message sent successfully 117 | default: 118 | // Failed to send, dropping message 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | func (stream *Event) serveHTTP() gin.HandlerFunc { 126 | return func(c *gin.Context) { 127 | // Initialize client channel 128 | clientChan := make(ClientChan) 129 | 130 | // Send new connection to event server 131 | stream.NewClients <- clientChan 132 | 133 | go func() { 134 | <-c.Writer.CloseNotify() 135 | 136 | // Drain client channel so that it does not block. Server may keep sending messages to this channel 137 | for range clientChan { 138 | } 139 | // Send closed connection to event server 140 | stream.ClosedClients <- clientChan 141 | }() 142 | 143 | c.Set("clientChan", clientChan) 144 | 145 | c.Next() 146 | } 147 | } 148 | 149 | func HeadersMiddleware() gin.HandlerFunc { 150 | return func(c *gin.Context) { 151 | c.Writer.Header().Set("Content-Type", "text/event-stream") 152 | c.Writer.Header().Set("Cache-Control", "no-cache") 153 | c.Writer.Header().Set("Connection", "keep-alive") 154 | c.Writer.Header().Set("Transfer-Encoding", "chunked") 155 | c.Next() 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /server-sent-event/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Server Sent Event 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /struct-lvl-validations/README.md: -------------------------------------------------------------------------------- 1 | # Struct level validations 2 | 3 | Validations can also be registered at the `struct` level when field level validations 4 | don't make much sense. This can also be used to solve cross-field validation elegantly. 5 | Additionally, it can be combined with tag validations. Struct Level validations run after 6 | the structs tag validations. 7 | 8 | ## Example requests 9 | 10 | ```shell 11 | # Validation errors are generated for struct tags as well as at the struct level 12 | $ curl -s -X POST http://localhost:8085/user \ 13 | -H 'content-type: application/json' \ 14 | -d '{}' | jq 15 | { 16 | "error": "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", 17 | "message": "User validation failed!" 18 | } 19 | 20 | # Validation fails at the struct level because neither first name nor last name are present 21 | $ curl -s -X POST http://localhost:8085/user \ 22 | -H 'content-type: application/json' \ 23 | -d '{"email": "george@vandaley.com"}' | jq 24 | { 25 | "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", 26 | "message": "User validation failed!" 27 | } 28 | 29 | # No validation errors when either first name or last name is present 30 | $ curl -X POST http://localhost:8085/user \ 31 | -H 'content-type: application/json' \ 32 | -d '{"fname": "George", "email": "george@vandaley.com"}' 33 | {"message":"User validation successful."} 34 | 35 | $ curl -X POST http://localhost:8085/user \ 36 | -H 'content-type: application/json' \ 37 | -d '{"lname": "Contanza", "email": "george@vandaley.com"}' 38 | {"message":"User validation successful."} 39 | 40 | $ curl -X POST http://localhost:8085/user \ 41 | -H 'content-type: application/json' \ 42 | -d '{"fname": "George", "lname": "Costanza", "email": "george@vandaley.com"}' 43 | {"message":"User validation successful."} 44 | ``` 45 | 46 | ### Useful links 47 | 48 | - [Validator docs](https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.RegisterStructValidation) 49 | - [Struct level example](https://github.com/go-playground/validator/blob/master/_examples/struct-level/main.go) 50 | - [Validator release notes](https://github.com/go-playground/validator/releases/tag/v10.7.0) 51 | -------------------------------------------------------------------------------- /struct-lvl-validations/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/gin-gonic/gin/binding" 8 | validator "github.com/go-playground/validator/v10" 9 | ) 10 | 11 | // User contains user information. 12 | type User struct { 13 | FirstName string `json:"fname"` 14 | LastName string `json:"lname"` 15 | Email string `binding:"required,email"` 16 | } 17 | 18 | // UserStructLevelValidation contains custom struct level validations that don't always 19 | // make sense at the field validation level. For example, this function validates that either 20 | // FirstName or LastName exist; could have done that with a custom field validation but then 21 | // would have had to add it to both fields duplicating the logic + overhead, this way it's 22 | // only validated once. 23 | // 24 | // NOTE: you may ask why wouldn't not just do this outside of validator. Doing this way 25 | // hooks right into validator and you can combine with validation tags and still have a 26 | // common error output format. 27 | func UserStructLevelValidation(sl validator.StructLevel) { 28 | // user := structLevel.CurrentStruct.Interface().(User) 29 | user := sl.Current().Interface().(User) 30 | 31 | if len(user.FirstName) == 0 && len(user.LastName) == 0 { 32 | sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "") 33 | sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "") 34 | } 35 | 36 | // plus can to more, even with different tag than "fnameorlname" 37 | } 38 | 39 | func main() { 40 | route := gin.Default() 41 | 42 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 43 | v.RegisterStructValidation(UserStructLevelValidation, User{}) 44 | } 45 | 46 | route.POST("/user", validateUser) 47 | route.Run(":8085") 48 | } 49 | 50 | func validateUser(c *gin.Context) { 51 | var u User 52 | if err := c.ShouldBindJSON(&u); err == nil { 53 | c.JSON(http.StatusOK, gin.H{"message": "User validation successful."}) 54 | } else { 55 | c.JSON(http.StatusBadRequest, gin.H{ 56 | "message": "User validation failed!", 57 | "error": err.Error(), 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /template/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func formatAsDate(t time.Time) string { 13 | year, month, day := t.Date() 14 | return fmt.Sprintf("%d%02d/%02d", year, month, day) 15 | } 16 | 17 | func main() { 18 | router := gin.Default() 19 | router.Delims("{[{", "}]}") 20 | router.SetFuncMap(template.FuncMap{ 21 | "formatAsDate": formatAsDate, 22 | }) 23 | router.LoadHTMLFiles("./testdata/raw.tmpl") 24 | 25 | router.GET("/raw", func(c *gin.Context) { 26 | c.HTML(http.StatusOK, "raw.tmpl", gin.H{ 27 | "now": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC), 28 | }) 29 | }) 30 | 31 | router.Run(":8080") 32 | } 33 | -------------------------------------------------------------------------------- /template/testdata/raw.tmpl: -------------------------------------------------------------------------------- 1 | Date: {[{.now | formatAsDate}]} 2 | -------------------------------------------------------------------------------- /upload-file/multiple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "path/filepath" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | router := gin.Default() 12 | // Set a lower memory limit for multipart forms (default is 32 MiB) 13 | router.MaxMultipartMemory = 8 << 20 // 8 MiB 14 | router.Static("/", "./public") 15 | router.POST("/upload", func(c *gin.Context) { 16 | name := c.PostForm("name") 17 | email := c.PostForm("email") 18 | 19 | // Multipart form 20 | form, err := c.MultipartForm() 21 | if err != nil { 22 | c.String(http.StatusBadRequest, "get form err: %s", err.Error()) 23 | return 24 | } 25 | files := form.File["files"] 26 | 27 | for _, file := range files { 28 | filename := filepath.Base(file.Filename) 29 | if err := c.SaveUploadedFile(file, filename); err != nil { 30 | c.String(http.StatusBadRequest, "upload file err: %s", err.Error()) 31 | return 32 | } 33 | } 34 | 35 | c.String(http.StatusOK, "Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email) 36 | }) 37 | router.Run(":8080") 38 | } 39 | -------------------------------------------------------------------------------- /upload-file/multiple/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Multiple file upload 6 | 7 | 8 |

Upload multiple files with fields

9 | 10 |
11 | Name:
12 | Email:
13 | Files:

14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /upload-file/single/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "path/filepath" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | router := gin.Default() 12 | // Set a lower memory limit for multipart forms (default is 32 MiB) 13 | router.MaxMultipartMemory = 8 << 20 // 8 MiB 14 | router.Static("/", "./public") 15 | router.POST("/upload", func(c *gin.Context) { 16 | name := c.PostForm("name") 17 | email := c.PostForm("email") 18 | 19 | // Source 20 | file, err := c.FormFile("file") 21 | if err != nil { 22 | c.String(http.StatusBadRequest, "get form err: %s", err.Error()) 23 | return 24 | } 25 | 26 | filename := filepath.Base(file.Filename) 27 | if err := c.SaveUploadedFile(file, filename); err != nil { 28 | c.String(http.StatusBadRequest, "upload file err: %s", err.Error()) 29 | return 30 | } 31 | 32 | c.String(http.StatusOK, "File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, name, email) 33 | }) 34 | router.Run(":8080") 35 | } 36 | -------------------------------------------------------------------------------- /upload-file/single/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single file upload 6 | 7 | 8 |

Upload single file with fields

9 | 10 |
11 | Name:
12 | Email:
13 | Files:

14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /versioning/README.md: -------------------------------------------------------------------------------- 1 | # Versioning 2 | 3 | This is Endpoint versioning (i.e `/v1/path`) example using custom middleware group in gin-gonic. 4 | 5 | ### How to Run? 6 | 7 | 1) Run ` go run main.go ` 8 | 9 | 2) Test APIs On ` http://localhost:8080 `. 10 | 11 | - For list users in v1, path should be `http://localhost:8080/v1/users`. Likewise, all other routes are working. 12 | -------------------------------------------------------------------------------- /versioning/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func main() { 10 | router := gin.Default() 11 | 12 | // version 1 13 | apiV1 := router.Group("/v1") 14 | 15 | apiV1.GET("users", func(c *gin.Context) { 16 | c.JSON(http.StatusOK, "List Of V1 Users") 17 | }) 18 | 19 | // User only can be added by authorized person 20 | authV1 := apiV1.Group("/", AuthMiddleWare()) 21 | 22 | authV1.POST("users/add", AddV1User) 23 | 24 | // version 2 25 | apiV2 := router.Group("/v2") 26 | 27 | apiV2.GET("users", func(c *gin.Context) { 28 | c.JSON(http.StatusOK, "List Of V2 Users") 29 | }) 30 | 31 | // User only can be added by authorized person 32 | authV2 := apiV2.Group("/", AuthMiddleWare()) 33 | 34 | authV2.POST("users/add", AddV2User) 35 | 36 | _ = router.Run(":8081") 37 | } 38 | 39 | func AddV1User(c *gin.Context) { 40 | // AddUser 41 | 42 | c.JSON(http.StatusOK, "V1 User added") 43 | } 44 | 45 | func AddV2User(c *gin.Context) { 46 | // AddUser 47 | 48 | c.JSON(http.StatusOK, "V2 User added") 49 | } 50 | 51 | func AuthMiddleWare() gin.HandlerFunc { 52 | return func(c *gin.Context) { 53 | // here you can add your authentication method to authorize users. 54 | username := c.PostForm("user") 55 | password := c.PostForm("password") 56 | 57 | if username == "foo" && password == "bar" { 58 | return 59 | } else { 60 | c.AbortWithStatus(http.StatusUnauthorized) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /websocket/README.md: -------------------------------------------------------------------------------- 1 | # WebSocket Example 2 | 3 | This project demonstrates a simple WebSocket implementation using the Gin framework and Gorilla WebSocket library in Go. It includes a server that handles WebSocket connections and a client that connects to the server and sends periodic messages. 4 | 5 | ## Project Overview 6 | 7 | The project consists of two main components: 8 | 9 | - **Server**: A WebSocket server that echoes messages back to the client. 10 | - **Client**: A WebSocket client that connects to the server, sends messages, and handles responses. 11 | 12 | ## Setup Instructions 13 | 14 | 1. **Clone the repository**: 15 | 16 | ```sh 17 | git clone https://github.com/your-repo/websocket-example.git 18 | cd websocket-example/websocket 19 | ``` 20 | 21 | 2. **Install dependencies**: 22 | Ensure you have Go installed, then run: 23 | 24 | ```sh 25 | go mod tidy 26 | ``` 27 | 28 | 3. **Run the server**: 29 | 30 | ```sh 31 | go run server/server.go 32 | ``` 33 | 34 | 4. **Run the client**: 35 | Open a new terminal and run: 36 | 37 | ```sh 38 | go run client/client.go 39 | ``` 40 | 41 | ## Usage Instructions 42 | 43 | ### Server 44 | 45 | The server listens on port 8080 by default and provides two endpoints: 46 | 47 | - `/echo`: Handles WebSocket connections and echoes messages back to the client. 48 | - `/`: Serves an HTML page for testing the WebSocket connection. 49 | 50 | ### Client 51 | 52 | The client connects to the server's `/echo` endpoint and sends periodic messages. It also handles incoming messages and logs them to the console. 53 | 54 | ## Code Explanation 55 | 56 | ### Server Implementation 57 | 58 | The server code is located in `server/server.go`. It uses the Gin framework to set up HTTP routes and the Gorilla WebSocket library to handle WebSocket connections. 59 | 60 | - **`echo` function**: Upgrades HTTP connections to WebSocket and echoes received messages back to the client. 61 | - **`home` function**: Serves an HTML page for testing the WebSocket connection. 62 | - **`main` function**: Sets up the Gin router, defines routes, and starts the server. 63 | 64 | ### Client Implementation 65 | 66 | The client code is located in `client/client.go`. It connects to the server's WebSocket endpoint and sends periodic messages. 67 | 68 | - **`main` function**: Parses command-line flags, sets up signal handling, and establishes a WebSocket connection. It sends periodic messages and handles incoming messages. 69 | -------------------------------------------------------------------------------- /websocket/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/url" 7 | "os" 8 | "os/signal" 9 | "time" 10 | 11 | "github.com/gorilla/websocket" 12 | ) 13 | 14 | var addr = flag.String("addr", "localhost:8080", "http service address") 15 | 16 | func main() { 17 | flag.Parse() 18 | log.SetFlags(0) 19 | 20 | interrupt := make(chan os.Signal, 1) 21 | signal.Notify(interrupt, os.Interrupt) 22 | 23 | u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"} 24 | log.Printf("connecting to %s", u.String()) 25 | 26 | c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) 27 | if err != nil { 28 | log.Fatal("dial:", err) 29 | } 30 | defer c.Close() 31 | 32 | done := make(chan struct{}) 33 | 34 | go func() { 35 | defer close(done) 36 | for { 37 | _, message, err := c.ReadMessage() 38 | if err != nil { 39 | log.Println("read:", err) 40 | return 41 | } 42 | log.Printf("recv: %s", message) 43 | } 44 | }() 45 | 46 | ticker := time.NewTicker(time.Second) 47 | defer ticker.Stop() 48 | 49 | for { 50 | select { 51 | case <-done: 52 | return 53 | case t := <-ticker.C: 54 | err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) 55 | if err != nil { 56 | log.Println("write:", err) 57 | return 58 | } 59 | case <-interrupt: 60 | log.Println("interrupt") 61 | 62 | // Cleanly close the connection by sending a close message and then 63 | // waiting (with timeout) for the server to close the connection. 64 | err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 65 | if err != nil { 66 | log.Println("write close:", err) 67 | return 68 | } 69 | select { 70 | case <-done: 71 | case <-time.After(time.Second): 72 | } 73 | return 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /websocket/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.0 7 | github.com/gorilla/websocket v1.5.3 8 | ) 9 | 10 | require ( 11 | github.com/bytedance/sonic v1.13.2 // indirect 12 | github.com/bytedance/sonic/loader v0.2.4 // indirect 13 | github.com/cloudwego/base64x v0.1.5 // indirect 14 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 15 | github.com/gin-contrib/sse v1.0.0 // indirect 16 | github.com/go-playground/locales v0.14.1 // indirect 17 | github.com/go-playground/universal-translator v0.18.1 // indirect 18 | github.com/go-playground/validator/v10 v10.25.0 // indirect 19 | github.com/goccy/go-json v0.10.5 // indirect 20 | github.com/json-iterator/go v1.1.12 // indirect 21 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 22 | github.com/leodido/go-urn v1.4.0 // indirect 23 | github.com/mattn/go-isatty v0.0.20 // indirect 24 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 25 | github.com/modern-go/reflect2 v1.0.2 // indirect 26 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 27 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 28 | github.com/ugorji/go/codec v1.2.12 // indirect 29 | golang.org/x/arch v0.15.0 // indirect 30 | golang.org/x/crypto v0.36.0 // indirect 31 | golang.org/x/net v0.38.0 // indirect 32 | golang.org/x/sys v0.31.0 // indirect 33 | golang.org/x/text v0.23.0 // indirect 34 | google.golang.org/protobuf v1.36.5 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /websocket/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 13 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 14 | github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= 15 | github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= 16 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 17 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 20 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 21 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 22 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 23 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 24 | github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= 25 | github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= 26 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 27 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 28 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 32 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 33 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 34 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 35 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 36 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 37 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 38 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 39 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 40 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 41 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 42 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 43 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 46 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 47 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 48 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 49 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 53 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 54 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 55 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 56 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 57 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 58 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 59 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 60 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 61 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 62 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 63 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 64 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 65 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 66 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 67 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 68 | golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= 69 | golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= 70 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 71 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 72 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 73 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 74 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 76 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 77 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 78 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 79 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 81 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 82 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 87 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 88 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 89 | -------------------------------------------------------------------------------- /websocket/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "text/template" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | var addr = flag.String("addr", ":8080", "http service address") 13 | 14 | var upgrader = websocket.Upgrader{} // use default option 15 | 16 | func echo(ctx *gin.Context) { 17 | w,r := ctx.Writer, ctx.Request 18 | c, err := upgrader.Upgrade(w, r, nil) 19 | if err != nil { 20 | log.Println("upgrade:", err) 21 | return 22 | } 23 | defer c.Close() 24 | for { 25 | mt, message, err := c.ReadMessage() 26 | if err != nil { 27 | log.Println("read:", err) 28 | break 29 | } 30 | log.Printf("recv:%s", message) 31 | err = c.WriteMessage(mt, message) 32 | if err != nil { 33 | log.Println("write:", err) 34 | break 35 | } 36 | } 37 | } 38 | 39 | func home(c *gin.Context) { 40 | homeTemplate.Execute(c.Writer, "ws://"+c.Request.Host+"/echo") 41 | } 42 | 43 | func main() { 44 | flag.Parse() 45 | log.SetFlags(0) 46 | r := gin.Default() 47 | r.GET("/echo", echo) 48 | r.GET("/", home) 49 | log.Fatal(r.Run(*addr)) 50 | } 51 | 52 | 53 | var homeTemplate = template.Must(template.New("").Parse(` 54 | 55 | 56 | 57 | 58 | 106 | 107 | 108 | 109 |
110 |

Click "Open" to create a connection to the server, 111 | "Send" to send a message to the server and "Close" to close the connection. 112 | You can change the message and send multiple times. 113 |

114 |

115 | 116 | 117 |

118 | 119 |

120 |
121 |
122 |
123 | 124 | 125 | `)) 126 | --------------------------------------------------------------------------------