├── .gitignore ├── LICENSE ├── README.md ├── chat_server ├── index.html.go ├── main.go └── session_manager.go ├── file_server └── main.go ├── go.mod ├── go.sum ├── https_server ├── cert.pem ├── key.pem └── main.go ├── redis_cli ├── README.md ├── main.go ├── redis_codec.go ├── redis_console.go └── redisgo │ ├── LICENSE │ ├── README.md │ ├── decode.go │ ├── decode_test.go │ ├── encode.go │ ├── encode_test.go │ └── resp.go └── tcp_server └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-netty-samples 2 | 3 | An open source samples written by [go-netty](https://github.com/go-netty/go-netty) 4 | 5 | ## Samples 6 | 7 | 1. [chat_server](./chat_server) - A simple web-based chatroom 8 | 2. [file_server](./file_server) - A simple web-based file browser 9 | 3. [tcp_server](./tcp_server) - A simple echo server & client 10 | 4. [redis_cli](./redis_cli) - A simple redis cli 11 | 5. [https_server](./https_server) - A simple https server 12 | -------------------------------------------------------------------------------- /chat_server/index.html.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | var indexHtml = []byte(` 20 | 21 | 22 | 23 | 24 | WebSocket Chat 25 | 26 | 27 | 62 |
63 |

WebSocket Chatroom:

64 | 65 |
66 | 67 | 68 | 69 | 70 |
71 |
72 |
73 | 74 | 75 | `) 76 | -------------------------------------------------------------------------------- /chat_server/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | 23 | "github.com/go-netty/go-netty" 24 | "github.com/go-netty/go-netty-transport/websocket" 25 | "github.com/go-netty/go-netty/codec/format" 26 | "github.com/go-netty/go-netty/codec/frame" 27 | ) 28 | 29 | var ManagerInst = NewManager() 30 | 31 | func main() { 32 | 33 | // index page. 34 | websocket.DefaultOptions.ServeMux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 35 | writer.Write(indexHtml) 36 | }) 37 | 38 | // child pipeline initializer. 39 | setupCodec := func(channel netty.Channel) { 40 | channel.Pipeline(). 41 | // read websocket message 42 | AddLast(frame.PacketCodec(128)). 43 | // decode bytes to map[string]interface{} 44 | AddLast(format.JSONCodec(true, false)). 45 | // session recorder. 46 | AddLast(ManagerInst). 47 | // chat handler. 48 | AddLast(chatHandler{}) 49 | } 50 | 51 | // setup bootstrap & startup server. 52 | netty.NewBootstrap(netty.WithChildInitializer(setupCodec), netty.WithTransport(websocket.New())). 53 | Listen("0.0.0.0:8080/chat").Sync() 54 | } 55 | 56 | type chatHandler struct{} 57 | 58 | func (chatHandler) HandleActive(ctx netty.ActiveContext) { 59 | type wsTransport interface { 60 | Route() string 61 | Header() http.Header 62 | } 63 | 64 | if wst, ok := ctx.Channel().Transport().(wsTransport); ok { 65 | fmt.Printf("child connection from: %s, route: %s, Websocket-Key: %s, User-Agent: %s\n", 66 | ctx.Channel().RemoteAddr(), wst.Route(), wst.Header().Get("Sec-Websocket-Key"), wst.Header().Get("User-Agent")) 67 | } 68 | 69 | ctx.HandleActive() 70 | } 71 | 72 | func (chatHandler) HandleRead(ctx netty.InboundContext, message netty.Message) { 73 | 74 | fmt.Printf("received child message from: %s, %v\n", ctx.Channel().RemoteAddr(), message) 75 | 76 | if cmd, ok := message.(map[string]interface{}); ok { 77 | cmd["id"] = ctx.Channel().ID() 78 | } 79 | 80 | ManagerInst.Broadcast(message) 81 | } 82 | 83 | func (chatHandler) HandleInactive(ctx netty.InactiveContext, ex netty.Exception) { 84 | fmt.Printf("child connection closed: %s %s\n", ctx.Channel().RemoteAddr(), ex.Error()) 85 | ctx.HandleInactive(ex) 86 | } 87 | -------------------------------------------------------------------------------- /chat_server/session_manager.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "sync" 21 | 22 | "github.com/go-netty/go-netty" 23 | ) 24 | 25 | type Manager interface { 26 | netty.ActiveHandler 27 | netty.InactiveHandler 28 | Size() int 29 | Context(id int64) netty.HandlerContext 30 | ForEach(func(netty.HandlerContext) bool) 31 | Broadcast(message netty.Message) 32 | BroadcastIf(message netty.Message, fn func(netty.HandlerContext) bool) 33 | } 34 | 35 | func NewManager() Manager { 36 | return &sessionManager{ 37 | _sessions: make(map[int64]netty.HandlerContext, 64), 38 | } 39 | } 40 | 41 | type sessionManager struct { 42 | _sessions map[int64]netty.HandlerContext 43 | _mutex sync.RWMutex 44 | } 45 | 46 | func (s *sessionManager) Size() int { 47 | s._mutex.RLock() 48 | size := len(s._sessions) 49 | s._mutex.RUnlock() 50 | return size 51 | } 52 | 53 | func (s *sessionManager) Context(id int64) netty.HandlerContext { 54 | s._mutex.RLock() 55 | ctx, _ := s._sessions[id] 56 | s._mutex.RUnlock() 57 | return ctx 58 | } 59 | 60 | func (s *sessionManager) ForEach(fn func(netty.HandlerContext) bool) { 61 | s._mutex.RLock() 62 | defer s._mutex.RUnlock() 63 | 64 | for _, ctx := range s._sessions { 65 | fn(ctx) 66 | } 67 | } 68 | 69 | func (s *sessionManager) Broadcast(message netty.Message) { 70 | s.ForEach(func(ctx netty.HandlerContext) bool { 71 | ctx.Write(message) 72 | return true 73 | }) 74 | } 75 | 76 | func (s *sessionManager) BroadcastIf(message netty.Message, fn func(netty.HandlerContext) bool) { 77 | s.ForEach(func(ctx netty.HandlerContext) bool { 78 | if fn(ctx) { 79 | ctx.Write(message) 80 | } 81 | return true 82 | }) 83 | } 84 | 85 | func (s *sessionManager) HandleActive(ctx netty.ActiveContext) { 86 | 87 | s._mutex.Lock() 88 | s._sessions[ctx.Channel().ID()] = ctx 89 | s._mutex.Unlock() 90 | 91 | ctx.HandleActive() 92 | } 93 | 94 | func (s *sessionManager) HandleInactive(ctx netty.InactiveContext, ex netty.Exception) { 95 | s._mutex.Lock() 96 | delete(s._sessions, ctx.Channel().ID()) 97 | s._mutex.Unlock() 98 | 99 | ctx.HandleInactive(ex) 100 | } 101 | -------------------------------------------------------------------------------- /file_server/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | 23 | "github.com/go-netty/go-netty" 24 | "github.com/go-netty/go-netty/codec/xhttp" 25 | ) 26 | 27 | func main() { 28 | 29 | // http file server handler. 30 | httpMux := http.NewServeMux() 31 | httpMux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("./")))) 32 | 33 | // channel pipeline initializer. 34 | setupCodec := func(channel netty.Channel) { 35 | channel.Pipeline(). 36 | // decode http request from channel 37 | AddLast(xhttp.ServerCodec()). 38 | // print http access log 39 | AddLast(new(httpStateHandler)). 40 | // compatible with http.Handler 41 | AddLast(xhttp.Handler(httpMux)) 42 | } 43 | 44 | // setup bootstrap & startup server. 45 | netty.NewBootstrap(netty.WithChildInitializer(setupCodec)). 46 | Listen("0.0.0.0:8080").Sync() 47 | } 48 | 49 | type httpStateHandler struct{} 50 | 51 | func (*httpStateHandler) HandleActive(ctx netty.ActiveContext) { 52 | fmt.Printf("http client active: %s\n", ctx.Channel().RemoteAddr()) 53 | ctx.HandleActive() 54 | } 55 | 56 | func (*httpStateHandler) HandleRead(ctx netty.InboundContext, message netty.Message) { 57 | if request, ok := message.(*http.Request); ok { 58 | fmt.Printf("[%d]%s: %s %s\n", ctx.Channel().ID(), ctx.Channel().RemoteAddr(), request.Method, request.URL.Path) 59 | } 60 | ctx.HandleRead(message) 61 | } 62 | 63 | func (*httpStateHandler) HandleInactive(ctx netty.InactiveContext, ex netty.Exception) { 64 | fmt.Printf("http client inactive: %s %v\n", ctx.Channel().RemoteAddr(), ex) 65 | ctx.HandleInactive(ex) 66 | } 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-netty/go-netty-samples 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/go-netty/go-netty v1.6.7 7 | github.com/go-netty/go-netty-transport v1.7.13 8 | ) 9 | 10 | require ( 11 | github.com/gobwas/httphead v0.1.0 // indirect 12 | github.com/gobwas/pool v0.2.1 // indirect 13 | github.com/gobwas/ws v1.4.0 // indirect 14 | golang.org/x/sys v0.28.0 // indirect 15 | ) 16 | 17 | //replace github.com/go-netty/go-netty => ../go-netty 18 | //replace github.com/go-netty/go-netty-transport => ../go-netty-transport 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-netty/go-netty v1.6.7 h1:heWNYCzAjiHnNZvLaoLyemHgZIWb0FSvZERYI3LnJqQ= 2 | github.com/go-netty/go-netty v1.6.7/go.mod h1:vSbL7RzFTO5bHXhxzZsAW0iStVz1qnenR90UVF6e1HA= 3 | github.com/go-netty/go-netty-transport v1.7.13 h1:46aR9f+Iosm3FHwNlz2u9/nWYoaV12EapDkEmfvlX6U= 4 | github.com/go-netty/go-netty-transport v1.7.13/go.mod h1:B0v+jKzmtQOiA1dagrm4Z5mn1CddkKPzoUDfQlSsjkk= 5 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 6 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 7 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 8 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 9 | github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= 10 | github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= 11 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 12 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 13 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 14 | -------------------------------------------------------------------------------- /https_server/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDazCCAlOgAwIBAgIUS0gnR4GQRAax+E+PaFiV2+ktXN8wDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAzMzAxNjMzMzRaFw0zMDAz 5 | MjgxNjMzMzRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB 7 | AQUAA4IBDwAwggEKAoIBAQCmJtTK0ujv9jDEhmhrOsSELj9i1+ZZHKDdZQrexZXZ 8 | /ipBzpk3HXiXeLOxwpmiQTgW7l+ocx/RoWmjr8/95ZCDgMYk2skMmJw5HbldQtaG 9 | c35Bu1w0xjo83vQRfLW/DhEgc6bN++4HWssmoex+PX5mNc5ZfdC+0apeGLuDv8Bl 10 | /B9BhF1gApbs3oKNy2NuU3T1z/jjymPRFHNbbfVNAN7Zzt5l9FH4yZosr1K5ZVHF 11 | yK1Aj76pdVE+P48TK8otOKZusUBGGeg7+Uyq3A8YrUJChr55MxZ8JolIOPBj13mS 12 | pQmnjNRpFrgwpfLqPKsK/gkGP8mpB0XVjQ54REHXytCdAgMBAAGjUzBRMB0GA1Ud 13 | DgQWBBSTjBPnKG4iDEN8VQ4QuT+lMxTR2jAfBgNVHSMEGDAWgBSTjBPnKG4iDEN8 14 | VQ4QuT+lMxTR2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAj 15 | R75wzk+44AY9dn+Qg9LxglT9ReZQ15qxUnAb30SEQNsrLY9i73acuLQlOQYenvAU 16 | 1mgeHinX4EFYOSp21azGZ3h9t4gy+MrnYtu26KasWc7KRrJRm0NfS8anRAESFMbK 17 | 0azUTcI+RTLZWAZXgUAtWacjUsxcoGmHIw/9aQ9fDtJ1+72WL/VNb2nlzUtB8j1r 18 | ZIct/CfEX6MRPhhpF38zOfE2i/5QcD9FJcZFHHbRU4ebJdcUKu+RnewYdDUsOPkv 19 | xZCjL/K15HTF++e5HNHnB3qDp6m9R9P79IlUck2+u18r/mBEhl5o/Xvp4yitvMc5 20 | OQfMbtCPeum72OkMnMDj 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /https_server/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEApibUytLo7/YwxIZoazrEhC4/YtfmWRyg3WUK3sWV2f4qQc6Z 3 | Nx14l3izscKZokE4Fu5fqHMf0aFpo6/P/eWQg4DGJNrJDJicOR25XULWhnN+Qbtc 4 | NMY6PN70EXy1vw4RIHOmzfvuB1rLJqHsfj1+ZjXOWX3QvtGqXhi7g7/AZfwfQYRd 5 | YAKW7N6CjctjblN09c/448pj0RRzW231TQDe2c7eZfRR+MmaLK9SuWVRxcitQI++ 6 | qXVRPj+PEyvKLTimbrFARhnoO/lMqtwPGK1CQoa+eTMWfCaJSDjwY9d5kqUJp4zU 7 | aRa4MKXy6jyrCv4JBj/JqQdF1Y0OeERB18rQnQIDAQABAoIBAFuQqNKB+xdLMMMN 8 | Y3w8rdI+fnYlj29qpIh+R8hPUW8KcBVGGw/RsQSg4z7ChNJzaMrSYWs+zrlDYCly 9 | GYjNu5+NFyV2YTgM3oNniyEHVZxRB7KH45+94phrMkx00uBQi0W8FqAMB7iL48sN 10 | j3xevUrsuiR9fs26VE55CNXplcldjiS7tcEIcCj557mwbmf4aJgwAtK7fMyxhGmM 11 | yThrSyG5GazZJ0nFCPcph46+gLjdXFoCqjLHlk98uSr4Ax+GYyFChY5V5MeY69BH 12 | 3TsCbhTsPB4tofOpO7goDdExitnl/chIfadNSnRtEPhQNNQiieYGQgAYoJxSMxI9 13 | 3lvLBmECgYEA02voNgwcSm1NQml8bXIeC+cHROMaessp0KEqRm4J4l8i1X62vPdu 14 | qbOR664LRJytcEOK8vFxf/DwiOtia2O1S4yJeDwYiktO0K0ozS7hUKK1gcMMZ6pg 15 | 5J1g0/NFhTLob0W5hRRx+byd+ZDQPiSf2v94Vzi5B+DXIUtWHJLAEDkCgYEAyS9a 16 | ZrQ7de6oh6gCxw8teY331rjOBhGaP8Ge0rwzg7ZjLHNJt0Mzfv+r3iLgbH/p2NTL 17 | NfV0s53ZqYJmNuuGcNZyyBjH4cDom8BR3RuJPMFgiyb+ufsmoj2P1YzVt10hIq8R 18 | v8qf+4TeZH8+h3MK1LSmAVwxO4vbrL3lX5Xve4UCgYBeMuXzrtGsF6ckv/tAiGf6 19 | xaTnfIh/zaWD30sGtmCRC/JpTVv+NgCIfAm4CIPcvjJIos92Dz2S07dYSgg/8N0G 20 | HEfj2mLUu1kSgRR81zZsxxI4Dr+oMLGbKsPJq8p93oDRky9lrAZcTz3TxEIEbi0L 21 | Vmg39H38lKFFpFtpv7jzgQKBgQCRp87zfJlwEJHDZUhdhgeOcdJ1iubeTfA3KJ8s 22 | Q0Wonmu+ytkb7XNjBwgPk4CYDSPo8GcPcd5EDIyBxP7r1ClJmHkRlZ9hOqEt4ldi 23 | lYUOj5KuLFVPoR2faKKyIXy5fJt42PcovDKHjvLUH6vNtOSqiv+FDJI4YlFxBysB 24 | wTPZYQKBgQCYnB1d1rHaEpDlgWx6WZnRvjK7S0cd1s8sX0zmexnhu8DK/iQpVFWb 25 | wXTnAFi0ljmHRjM+od6PCRVZ/V9iZIcsRmMhbaGcBMuRIQ5ASq1LVnTbXoQwbO+C 26 | nsHwLvd8nHfA9LyBSek9d7llK1oNeNRRQQW6b5X3sJWbBFAS6VCthQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /https_server/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | 23 | "github.com/go-netty/go-netty" 24 | "github.com/go-netty/go-netty-transport/tls" 25 | "github.com/go-netty/go-netty/codec/xhttp" 26 | ) 27 | 28 | func main() { 29 | 30 | httpMux := http.NewServeMux() 31 | httpMux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 32 | writer.Write([]byte("Hello, go-netty!")) 33 | }) 34 | 35 | // child pipeline initializer. 36 | setupCodec := func(channel netty.Channel) { 37 | channel.Pipeline(). 38 | // decode http request from channel 39 | AddLast(xhttp.ServerCodec()). 40 | // print http access log 41 | AddLast(new(httpStateHandler)). 42 | // compatible with http.Handler 43 | AddLast(xhttp.Handler(httpMux)) 44 | } 45 | 46 | // setup bootstrap & startup server. 47 | netty.NewBootstrap(netty.WithChildInitializer(setupCodec), netty.WithTransport(tls.New())). 48 | Listen("0.0.0.0:8080", tls.WithOptions(&tls.Options{CertFile: "cert.pem", KeyFile: "key.pem"})).Sync() 49 | } 50 | 51 | type httpStateHandler struct{} 52 | 53 | func (*httpStateHandler) HandleActive(ctx netty.ActiveContext) { 54 | fmt.Printf("http client active: %s\n", ctx.Channel().RemoteAddr()) 55 | ctx.HandleActive() 56 | } 57 | 58 | func (*httpStateHandler) HandleRead(ctx netty.InboundContext, message netty.Message) { 59 | if request, ok := message.(*http.Request); ok { 60 | fmt.Printf("[%d]%s: %s %s\n", ctx.Channel().ID(), ctx.Channel().RemoteAddr(), request.Method, request.URL.Path) 61 | } 62 | ctx.HandleRead(message) 63 | } 64 | 65 | func (*httpStateHandler) HandleInactive(ctx netty.InactiveContext, ex netty.Exception) { 66 | fmt.Printf("http client inactive: %s %v\n", ctx.Channel().RemoteAddr(), ex) 67 | ctx.HandleInactive(ex) 68 | } 69 | -------------------------------------------------------------------------------- /redis_cli/README.md: -------------------------------------------------------------------------------- 1 | # redis_cli 2 | A simple redis cli written by [go-netty](https://github.com/go-netty/go-netty) 3 | 4 | ### Preview 5 | ```bash 6 | connecting redis server ... 7 | connected 8 | 192.168.212.212:6379>get name 9 | (empty) 10 | 192.168.212.212:6379>set name go-netty 11 | OK 12 | 192.168.212.212:6379>get name 13 | go-netty 14 | 192.168.212.212:6379>exit 15 | exited 16 | ``` 17 | -------------------------------------------------------------------------------- /redis_cli/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/go-netty/go-netty" 23 | "github.com/go-netty/go-netty/utils" 24 | ) 25 | 26 | func main() { 27 | 28 | // setup client pipeline initializer. 29 | setupCodec := func(channel netty.Channel) { 30 | channel.Pipeline(). 31 | AddLast(&simpleRedisCodec{}, &simpleRedisConsole{}) 32 | } 33 | 34 | // new bootstrap 35 | var bootstrap = netty.NewBootstrap(netty.WithClientInitializer(setupCodec)) 36 | 37 | // connect to redis server 38 | fmt.Println("connecting redis server ...") 39 | 40 | ch, err := bootstrap.Connect("127.0.0.1:6379") 41 | utils.Assert(err) 42 | 43 | select { 44 | case <-ch.Context().Done(): 45 | case <-bootstrap.Context().Done(): 46 | } 47 | 48 | fmt.Println("exited") 49 | } 50 | -------------------------------------------------------------------------------- /redis_cli/redis_codec.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "github.com/go-netty/go-netty" 23 | "github.com/go-netty/go-netty-samples/redis_cli/redisgo" 24 | "github.com/go-netty/go-netty/utils" 25 | "io" 26 | ) 27 | 28 | type simpleRedisCodec struct { 29 | decoder *redisgo.Decoder 30 | } 31 | 32 | func (s *simpleRedisCodec) CodecName() string { 33 | return "simple-redis-codec" 34 | } 35 | 36 | func (s *simpleRedisCodec) HandleRead(ctx netty.InboundContext, message netty.Message) { 37 | 38 | // init decoder. 39 | if nil == s.decoder { 40 | s.decoder = redisgo.NewDecoder(message.(io.Reader), 10240) 41 | } 42 | 43 | // decode redis response. 44 | resp := &redisgo.Resp{} 45 | utils.Assert(s.decoder.Decode(resp)) 46 | 47 | // fmt.Println("->", resp.String()) 48 | // post response. 49 | ctx.HandleRead(resp) 50 | } 51 | 52 | func (s *simpleRedisCodec) HandleWrite(ctx netty.OutboundContext, message netty.Message) { 53 | 54 | switch v := message.(type) { 55 | case []redisgo.Value: 56 | // encode request. 57 | buffer := bytes.NewBuffer(nil) 58 | utils.Assert(redisgo.EncodeMulti(buffer, v...)) 59 | 60 | // fmt.Println("<-", buffer.String()) 61 | // post request. 62 | ctx.HandleWrite(buffer) 63 | default: 64 | utils.Assert(fmt.Errorf("%T is invalid message", message)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /redis_cli/redis_console.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "bufio" 21 | "fmt" 22 | "os" 23 | "strings" 24 | 25 | "github.com/go-netty/go-netty" 26 | "github.com/go-netty/go-netty-samples/redis_cli/redisgo" 27 | ) 28 | 29 | type simpleRedisConsole struct { 30 | respChan chan *redisgo.Resp 31 | } 32 | 33 | func (s *simpleRedisConsole) HandleActive(ctx netty.ActiveContext) { 34 | fmt.Println("connected") 35 | 36 | go s.attachConsole(ctx) 37 | ctx.HandleActive() 38 | } 39 | 40 | func (s *simpleRedisConsole) HandleRead(ctx netty.InboundContext, message netty.Message) { 41 | s.respChan <- message.(*redisgo.Resp) 42 | } 43 | 44 | func (s *simpleRedisConsole) HandleInactive(ctx netty.InactiveContext, ex netty.Exception) { 45 | fmt.Println("disconnected", ex) 46 | 47 | ctx.HandleInactive(ex) 48 | } 49 | 50 | func (s *simpleRedisConsole) attachConsole(ctx netty.HandlerContext) { 51 | 52 | s.respChan = make(chan *redisgo.Resp, 1) 53 | 54 | fmt.Print(ctx.Channel().RemoteAddr(), ">") 55 | 56 | stdin := bufio.NewScanner(os.Stdin) 57 | for stdin.Scan() { 58 | 59 | if text := strings.TrimSpace(stdin.Text()); len(text) > 0 { 60 | 61 | switch text { 62 | case "exit": 63 | ctx.Close(fmt.Errorf("user exit")) 64 | return 65 | case "help": 66 | fmt.Println("help information") 67 | default: 68 | inputs := strings.Split(text, " ") 69 | 70 | // build command. 71 | var cmds = make([]redisgo.Value, 0, len(inputs)) 72 | for _, v := range inputs { 73 | cmds = append(cmds, redisgo.BlukString(v)) 74 | } 75 | 76 | // send redis command. 77 | ctx.Write(cmds) 78 | 79 | // print response 80 | resp := <-s.respChan 81 | 82 | switch { 83 | case resp.Null: 84 | fmt.Println("(empty)") 85 | case len(resp.Data) > 0: 86 | fmt.Println(resp.Data) 87 | case len(resp.Array) > 0: 88 | for index, rsp := range resp.Array { 89 | fmt.Println(fmt.Sprintf(`%d) "%s"`, index+1, rsp.Data)) 90 | } 91 | } 92 | } 93 | } else { 94 | fmt.Println("please input redis command") 95 | } 96 | 97 | fmt.Print(ctx.Channel().RemoteAddr(), ">") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /redis_cli/redisgo/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 vizee 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 | 23 | -------------------------------------------------------------------------------- /redis_cli/redisgo/README.md: -------------------------------------------------------------------------------- 1 | # redisgo 2 | 3 | yet another redis protocol codec 4 | 5 | COPY FROM [https://github.com/rokumoe/redisgo](https://github.com/rokumoe/redisgo) 6 | 7 | * fix decode issue -------------------------------------------------------------------------------- /redis_cli/redisgo/decode.go: -------------------------------------------------------------------------------- 1 | package redisgo 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "strconv" 8 | "unsafe" 9 | ) 10 | 11 | const ( 12 | maxBlukSize = 512 * 1024 * 1024 13 | ) 14 | 15 | type Decoder struct { 16 | r *bufio.Reader 17 | } 18 | 19 | func (d *Decoder) readLine() ([]byte, error) { 20 | ln, err := d.r.ReadSlice('\n') 21 | if err != nil { 22 | return nil, err 23 | } 24 | if len(ln) < 2 || ln[len(ln)-2] != '\r' { 25 | return nil, fmt.Errorf("expect terminated with CRLF") 26 | } 27 | return ln[:len(ln)-2], nil 28 | } 29 | 30 | func (d *Decoder) Decode(r *Resp) error { 31 | ch, err := d.r.ReadByte() 32 | if err != nil { 33 | return err 34 | } 35 | switch RespKind(ch) { 36 | case SimpleKind, ErrorKind, IntegerKind: 37 | ln, err := d.readLine() 38 | if err != nil { 39 | return err 40 | } 41 | *r = Resp{ 42 | Kind: RespKind(ch), 43 | Data: string(ln), 44 | } 45 | case BlukKind: 46 | ln, err := d.readLine() 47 | if err != nil { 48 | return err 49 | } 50 | n, err := strconv.Atoi(*(*string)(unsafe.Pointer(&ln))) 51 | if err != nil { 52 | return err 53 | } 54 | if n < -1 || n > maxBlukSize { 55 | return fmt.Errorf("invalid bluk length: %d", n) 56 | } 57 | if n == -1 { 58 | *r = Resp{ 59 | Kind: RespKind(ch), 60 | Null: true, 61 | } 62 | } else { 63 | data := make([]byte, n+2) 64 | _, err := io.ReadFull(d.r, data) 65 | if err != nil { 66 | return err 67 | } 68 | if data[len(data)-2] != '\r' || data[len(data)-1] != '\n' { 69 | return fmt.Errorf("expect terminated with CRLF") 70 | } 71 | *r = Resp{ 72 | Kind: RespKind(ch), 73 | Data: string(data[:len(data)-2]), 74 | } 75 | } 76 | case ArrayKind: 77 | ln, err := d.readLine() 78 | if err != nil { 79 | return err 80 | } 81 | n, err := strconv.Atoi(*(*string)(unsafe.Pointer(&ln))) 82 | if err != nil { 83 | return err 84 | } 85 | if n < 0 { 86 | return fmt.Errorf("invalid array length: %d", n) 87 | } 88 | array := make([]Resp, n) 89 | for i := 0; i < n; i++ { 90 | err = d.Decode(&array[i]) 91 | if err != nil { 92 | return err 93 | } 94 | } 95 | *r = Resp{ 96 | Kind: RespKind(ch), 97 | Array: array, 98 | } 99 | default: 100 | return fmt.Errorf("unrecognized kind: %c", ch) 101 | } 102 | return nil 103 | } 104 | 105 | func NewDecoder(r io.Reader, maxLineSize int) *Decoder { 106 | br, ok := r.(*bufio.Reader) 107 | if !ok { 108 | br = bufio.NewReaderSize(r, maxLineSize) 109 | } 110 | return &Decoder{ 111 | r: br, 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /redis_cli/redisgo/decode_test.go: -------------------------------------------------------------------------------- 1 | package redisgo 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestDecoder_Decode(t *testing.T) { 10 | type args struct { 11 | p *Resp 12 | } 13 | tests := []struct { 14 | name string 15 | input string 16 | args args 17 | wantErr bool 18 | }{ 19 | {"simple", "+OK\r\n", args{&Resp{}}, false}, 20 | {"error", "-ERR unknown command 'foobar'\r\n", args{&Resp{}}, false}, 21 | {"integer", ":1000\r\n", args{&Resp{}}, false}, 22 | {"bluk", "$7\r\nfoo\nbar\r\n", args{&Resp{}}, false}, 23 | {"array", "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n", args{&Resp{}}, false}, 24 | {"array-in-array", "*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Foo\r\n-Bar\r\n", args{&Resp{}}, false}, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | d := NewDecoder(strings.NewReader(tt.input), 1024) 29 | if err := d.Decode(tt.args.p); (err != nil) != tt.wantErr { 30 | t.Errorf("Decoder.Decode() error = %v, wantErr %v", err, tt.wantErr) 31 | } else { 32 | t.Logf("%s", strconv.Quote(tt.args.p.String())) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /redis_cli/redisgo/encode.go: -------------------------------------------------------------------------------- 1 | package redisgo 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | blukchar = '$' 12 | integerchar = ':' 13 | arraychar = '*' 14 | ) 15 | 16 | var ( 17 | crlf = []byte{'\r', '\n'} 18 | simplechar = []byte{'+'} 19 | errorchar = []byte{'-'} 20 | nullbluk = []byte("$-1\r\n") 21 | ) 22 | 23 | type kind uint 24 | 25 | const ( 26 | nullKind kind = iota 27 | simpleKind 28 | errorKind 29 | blukKind 30 | intKind 31 | int8Kind 32 | int16Kind 33 | int32Kind 34 | int64Kind 35 | uintKind 36 | uint8Kind 37 | uint16Kind 38 | uint32Kind 39 | uint64Kind 40 | ) 41 | 42 | type Value struct { 43 | kind kind 44 | u64 uint64 45 | s string 46 | } 47 | 48 | func Null() Value { 49 | return Value{kind: nullKind} 50 | } 51 | 52 | func Simple(s string) Value { 53 | return Value{kind: simpleKind, s: s} 54 | } 55 | 56 | func Error(err string) Value { 57 | return Value{kind: errorKind, s: err} 58 | } 59 | 60 | func BlukString(s string) Value { 61 | return Value{kind: blukKind, s: s} 62 | } 63 | 64 | func Bluk(p []byte) Value { 65 | return BlukString(*(*string)(unsafe.Pointer(&p))) 66 | } 67 | 68 | func Int(v int) Value { 69 | return Value{kind: intKind, u64: uint64(v)} 70 | } 71 | 72 | func Int8(v int8) Value { 73 | return Value{kind: int8Kind, u64: uint64(v)} 74 | } 75 | 76 | func Int16(v int16) Value { 77 | return Value{kind: int16Kind, u64: uint64(v)} 78 | } 79 | 80 | func Int32(v int32) Value { 81 | return Value{kind: int32Kind, u64: uint64(v)} 82 | } 83 | 84 | func Int64(v int64) Value { 85 | return Value{kind: int64Kind, u64: uint64(v)} 86 | } 87 | 88 | func Uint(v uint) Value { 89 | return Value{kind: uintKind, u64: uint64(v)} 90 | } 91 | 92 | func Uint8(v uint8) Value { 93 | return Value{kind: uint8Kind, u64: uint64(v)} 94 | } 95 | 96 | func Uint16(v uint16) Value { 97 | return Value{kind: uint16Kind, u64: uint64(v)} 98 | } 99 | 100 | func Uint32(v uint32) Value { 101 | return Value{kind: uint32Kind, u64: uint64(v)} 102 | } 103 | 104 | func Uint64(v uint64) Value { 105 | return Value{kind: uint64Kind, u64: uint64(v)} 106 | } 107 | 108 | type slice struct { 109 | data uintptr 110 | n1 int 111 | n2 int 112 | } 113 | 114 | func sb(s string) []byte { 115 | b := slice{ 116 | data: *(*uintptr)(unsafe.Pointer(&s)), 117 | n1: len(s), 118 | n2: len(s), 119 | } 120 | return *(*[]byte)(unsafe.Pointer(&b)) 121 | } 122 | 123 | func Encode(w io.Writer, v Value) (err error) { 124 | var buf [32]byte 125 | b := crlf 126 | switch v.kind { 127 | case nullKind: 128 | b = nullbluk 129 | case simpleKind: 130 | _, err = w.Write(simplechar) 131 | if err != nil { 132 | return 133 | } 134 | _, err = w.Write(sb(v.s)) 135 | if err != nil { 136 | return 137 | } 138 | case errorKind: 139 | _, err = w.Write(errorchar) 140 | if err != nil { 141 | return 142 | } 143 | _, err = w.Write(sb(v.s)) 144 | if err != nil { 145 | return 146 | } 147 | case blukKind: 148 | t := append(buf[:0], blukchar) 149 | t = strconv.AppendInt(t, int64(len(v.s)), 10) 150 | t = append(t, '\r', '\n') 151 | _, err = w.Write(t) 152 | if err != nil { 153 | return 154 | } 155 | _, err = w.Write(sb(v.s)) 156 | if err != nil { 157 | return 158 | } 159 | case intKind: 160 | b = append(buf[:0], integerchar) 161 | b = strconv.AppendInt(b, int64(int(v.u64)), 10) 162 | b = append(b, '\r', '\n') 163 | case int8Kind: 164 | b = append(buf[:0], integerchar) 165 | b = strconv.AppendInt(b, int64(int8(v.u64)), 10) 166 | b = append(b, '\r', '\n') 167 | case int16Kind: 168 | b = append(buf[:0], integerchar) 169 | b = strconv.AppendInt(b, int64(int16(v.u64)), 10) 170 | b = append(b, '\r', '\n') 171 | case int32Kind: 172 | b = append(buf[:0], integerchar) 173 | b = strconv.AppendInt(b, int64(int32(v.u64)), 10) 174 | b = append(b, '\r', '\n') 175 | case int64Kind: 176 | b = append(buf[:0], integerchar) 177 | b = strconv.AppendInt(b, int64(int64(v.u64)), 10) 178 | b = append(b, '\r', '\n') 179 | case uintKind: 180 | b = append(buf[:0], integerchar) 181 | b = strconv.AppendUint(b, uint64(uint(v.u64)), 10) 182 | b = append(b, '\r', '\n') 183 | case uint8Kind: 184 | b = append(buf[:0], integerchar) 185 | b = strconv.AppendUint(b, uint64(uint8(v.u64)), 10) 186 | b = append(b, '\r', '\n') 187 | case uint16Kind: 188 | b = append(buf[:0], integerchar) 189 | b = strconv.AppendUint(b, uint64(uint16(v.u64)), 10) 190 | b = append(b, '\r', '\n') 191 | case uint32Kind: 192 | b = append(buf[:0], integerchar) 193 | b = strconv.AppendUint(b, uint64(uint32(v.u64)), 10) 194 | b = append(b, '\r', '\n') 195 | case uint64Kind: 196 | b = append(buf[:0], integerchar) 197 | b = strconv.AppendUint(b, uint64(uint64(v.u64)), 10) 198 | b = append(b, '\r', '\n') 199 | default: 200 | panic("never get here") 201 | } 202 | _, err = w.Write(b) 203 | return 204 | } 205 | 206 | func EncodeMulti(w io.Writer, vals ...Value) error { 207 | var buf [32]byte 208 | t := append(buf[:0], arraychar) 209 | t = strconv.AppendInt(t, int64(len(vals)), 10) 210 | t = append(t, '\r', '\n') 211 | _, err := w.Write(t) 212 | if err != nil { 213 | return err 214 | } 215 | for _, v := range vals { 216 | err = Encode(w, v) 217 | if err != nil { 218 | return err 219 | } 220 | } 221 | return nil 222 | } 223 | 224 | func EncodeResp(w io.Writer, r *Resp) (err error) { 225 | var buf [32]byte 226 | b := crlf 227 | switch r.Kind { 228 | case SimpleKind, ErrorKind, IntegerKind: 229 | if 3+len(r.Data) <= 32 { 230 | t := append(buf[:0], byte(r.Kind)) 231 | t = append(t, r.Data...) 232 | b = append(t, '\r', '\n') 233 | } else { 234 | _, err = w.Write([]byte{byte(r.Kind)}) 235 | if err != nil { 236 | return 237 | } 238 | _, err = w.Write(sb(r.Data)) 239 | if err != nil { 240 | return 241 | } 242 | } 243 | case BlukKind: 244 | if r.Null { 245 | b = nullbluk 246 | } else { 247 | t := append(buf[:0], '$') 248 | t = strconv.AppendInt(t, int64(len(r.Data)), 10) 249 | t = append(t, '\r', '\n') 250 | _, err = w.Write(t) 251 | if err != nil { 252 | return 253 | } 254 | _, err = w.Write(sb(r.Data)) 255 | if err != nil { 256 | return 257 | } 258 | } 259 | case ArrayKind: 260 | t := append(buf[:0], '*') 261 | t = strconv.AppendInt(t, int64(len(r.Array)), 10) 262 | t = append(t, '\r', '\n') 263 | _, err = w.Write(t) 264 | if err != nil { 265 | return 266 | } 267 | for i := range r.Array { 268 | err = EncodeResp(w, &r.Array[i]) 269 | if err != nil { 270 | return 271 | } 272 | } 273 | return 274 | default: 275 | return fmt.Errorf("unrecognized kind: %c", r.Kind) 276 | } 277 | _, err = w.Write(b) 278 | return 279 | } 280 | -------------------------------------------------------------------------------- /redis_cli/redisgo/encode_test.go: -------------------------------------------------------------------------------- 1 | package redisgo 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestEncode(t *testing.T) { 9 | type args struct { 10 | v Value 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | wantW string 16 | wantErr bool 17 | }{ 18 | {"null", args{Null()}, "$-1\r\n", false}, 19 | {"simple", args{Simple("hello")}, "+hello\r\n", false}, 20 | {"error", args{Error("ERR oops")}, "-ERR oops\r\n", false}, 21 | {"bluk", args{Bluk([]byte("hello"))}, "$5\r\nhello\r\n", false}, 22 | {"int", args{Int(233)}, ":233\r\n", false}, 23 | } 24 | for _, tt := range tests { 25 | t.Run(tt.name, func(t *testing.T) { 26 | w := &bytes.Buffer{} 27 | if err := Encode(w, tt.args.v); (err != nil) != tt.wantErr { 28 | t.Errorf("Encode() error = %v, wantErr %v", err, tt.wantErr) 29 | return 30 | } 31 | if gotW := w.String(); gotW != tt.wantW { 32 | t.Errorf("Encode() = %v, want %v", gotW, tt.wantW) 33 | } 34 | }) 35 | } 36 | } 37 | 38 | func TestEncodeMulti(t *testing.T) { 39 | type args struct { 40 | vals []Value 41 | } 42 | tests := []struct { 43 | name string 44 | args args 45 | wantW string 46 | wantErr bool 47 | }{ 48 | {"multi", args{[]Value{Null(), Simple("hello"), Int(233)}}, "*3\r\n$-1\r\n+hello\r\n:233\r\n", false}, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | w := &bytes.Buffer{} 53 | if err := EncodeMulti(w, tt.args.vals...); (err != nil) != tt.wantErr { 54 | t.Errorf("EncodeMulti() error = %v, wantErr %v", err, tt.wantErr) 55 | return 56 | } 57 | if gotW := w.String(); gotW != tt.wantW { 58 | t.Errorf("EncodeMulti() = %v, want %v", gotW, tt.wantW) 59 | } 60 | }) 61 | } 62 | } 63 | 64 | func TestEncodeResp(t *testing.T) { 65 | type args struct { 66 | r *Resp 67 | } 68 | tests := []struct { 69 | name string 70 | args args 71 | wantW string 72 | wantErr bool 73 | }{ 74 | {"array-in-array", args{&Resp{ 75 | Kind: ArrayKind, 76 | Array: []Resp{ 77 | { 78 | Kind: ArrayKind, 79 | Array: []Resp{ 80 | Resp{ 81 | Kind: IntegerKind, 82 | Data: "1", 83 | }, 84 | Resp{ 85 | Kind: IntegerKind, 86 | Data: "2", 87 | }, 88 | Resp{ 89 | Kind: IntegerKind, 90 | Data: "3", 91 | }, 92 | }, 93 | }, 94 | { 95 | Kind: ArrayKind, 96 | Array: []Resp{ 97 | { 98 | Kind: SimpleKind, 99 | Data: "Foo", 100 | }, 101 | { 102 | Kind: ErrorKind, 103 | Data: "Bar", 104 | }, 105 | }, 106 | }, 107 | }, 108 | }}, "*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Foo\r\n-Bar\r\n", false}, 109 | } 110 | for _, tt := range tests { 111 | t.Run(tt.name, func(t *testing.T) { 112 | w := &bytes.Buffer{} 113 | if err := EncodeResp(w, tt.args.r); (err != nil) != tt.wantErr { 114 | t.Errorf("EncodeResp() error = %v, wantErr %v", err, tt.wantErr) 115 | return 116 | } 117 | if gotW := w.String(); gotW != tt.wantW { 118 | t.Errorf("EncodeResp() = %v, want %v", gotW, tt.wantW) 119 | } 120 | }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /redis_cli/redisgo/resp.go: -------------------------------------------------------------------------------- 1 | package redisgo 2 | 3 | import "bytes" 4 | 5 | type RespKind byte 6 | 7 | const ( 8 | SimpleKind RespKind = '+' 9 | ErrorKind = '-' 10 | IntegerKind = ':' 11 | BlukKind = '$' 12 | ArrayKind = '*' 13 | ) 14 | 15 | type Resp struct { 16 | Kind RespKind 17 | Null bool 18 | Data string 19 | Array []Resp 20 | } 21 | 22 | func (r *Resp) String() string { 23 | w := &bytes.Buffer{} 24 | EncodeResp(w, r) 25 | return w.String() 26 | } 27 | -------------------------------------------------------------------------------- /tcp_server/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the go-netty project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/binary" 21 | "fmt" 22 | "time" 23 | 24 | "github.com/go-netty/go-netty" 25 | "github.com/go-netty/go-netty/codec/format" 26 | "github.com/go-netty/go-netty/codec/frame" 27 | "github.com/go-netty/go-netty/utils" 28 | ) 29 | 30 | func main() { 31 | 32 | // setup child pipeline initializer. 33 | childInitializer := func(channel netty.Channel) { 34 | channel.Pipeline(). 35 | AddLast(frame.LengthFieldCodec(binary.LittleEndian, 1024, 0, 2, 0, 2)). 36 | AddLast(format.TextCodec()). 37 | AddLast(EchoHandler{"Server"}) 38 | } 39 | 40 | // setup client pipeline initializer. 41 | clientInitializer := func(channel netty.Channel) { 42 | channel.Pipeline(). 43 | AddLast(frame.LengthFieldCodec(binary.LittleEndian, 1024, 0, 2, 0, 2)). 44 | AddLast(format.TextCodec()). 45 | AddLast(EchoHandler{"Client"}) 46 | } 47 | 48 | // new bootstrap 49 | var bootstrap = netty.NewBootstrap(netty.WithChildInitializer(childInitializer), netty.WithClientInitializer(clientInitializer)) 50 | 51 | // connect to the server after 1 second 52 | time.AfterFunc(time.Second, func() { 53 | _, err := bootstrap.Connect("127.0.0.1:6565") 54 | utils.Assert(err) 55 | }) 56 | 57 | // setup bootstrap & startup server. 58 | bootstrap.Listen("0.0.0.0:6565").Sync() 59 | } 60 | 61 | type EchoHandler struct { 62 | role string 63 | } 64 | 65 | func (l EchoHandler) HandleActive(ctx netty.ActiveContext) { 66 | fmt.Println(l.role, "->", "active:", ctx.Channel().RemoteAddr()) 67 | 68 | ctx.Write("Hello I'm " + l.role) 69 | ctx.HandleActive() 70 | } 71 | 72 | func (l EchoHandler) HandleRead(ctx netty.InboundContext, message netty.Message) { 73 | fmt.Println(l.role, "->", "handle read:", message) 74 | ctx.HandleRead(message) 75 | } 76 | 77 | func (l EchoHandler) HandleInactive(ctx netty.InactiveContext, ex netty.Exception) { 78 | fmt.Println(l.role, "->", "inactive:", ctx.Channel().RemoteAddr(), ex) 79 | ctx.HandleInactive(ex) 80 | } 81 | --------------------------------------------------------------------------------