├── LICENSE.txt ├── README.md ├── addons ├── discovery │ ├── discoverer.go │ ├── doc.go │ └── static │ │ └── static_discoverer.go ├── log │ ├── log_stream.go │ └── logger.go ├── transport │ └── http │ │ ├── client.go │ │ ├── client_test.go │ │ ├── config │ │ ├── config.go │ │ ├── error_codes.go │ │ └── gen_http_transport_config │ │ │ └── main.go │ │ ├── listener.go │ │ ├── listener_test.go │ │ └── serialization │ │ ├── gogo_proto │ │ ├── Transport.pb.go │ │ ├── Transport.proto │ │ ├── generate_proto.sh │ │ └── gogo_proto_serializer.go │ │ ├── json │ │ ├── json_serializer.go │ │ └── json_serializer_test.go │ │ └── serializer.go └── util │ ├── func_service.go │ ├── func_service_test.go │ └── nano_error.go ├── examples └── example1 │ ├── api │ ├── svc1 │ │ ├── http_transport_config.json │ │ └── requests.proto │ ├── svc2 │ │ ├── http_transport_config.json │ │ └── requests.proto │ ├── svc3 │ │ ├── http_transport_config.json │ │ └── requests.proto │ └── svc4 │ │ ├── http_transport_config.json │ │ └── requests.proto │ ├── api_go │ ├── generate.sh │ ├── svc1 │ │ ├── http_transport_config.go │ │ └── requests.pb.go │ ├── svc2 │ │ ├── http_transport_config.go │ │ └── requests.pb.go │ ├── svc3 │ │ ├── http_transport_config.go │ │ └── requests.pb.go │ └── svc4 │ │ ├── http_transport_config.go │ │ └── requests.pb.go │ ├── config │ ├── README.md │ ├── client │ │ └── config.go │ ├── common │ │ └── config.go │ ├── server │ │ └── config.go │ └── test │ │ └── config.go │ ├── servers │ ├── server1 │ │ └── main.go │ ├── server2a │ │ └── main.go │ ├── server2b │ │ └── main.go │ ├── server3a │ │ └── main.go │ ├── server3b │ │ └── main.go │ ├── server3c │ │ └── main.go │ ├── server3d │ │ └── main.go │ ├── test_client │ │ └── main.go │ └── tests │ │ ├── README.md │ │ ├── helpers │ │ ├── build_all.sh │ │ ├── build_helper.sh │ │ ├── env.sh │ │ ├── shutdown_servers.sh │ │ ├── start_servers.sh │ │ └── test.sh │ │ ├── test_server1.sh │ │ ├── test_server2.sh │ │ └── test_server3.sh │ └── services │ ├── svc1 │ └── svc.go │ ├── svc2 │ ├── svc.go │ └── tests │ │ ├── test1 │ │ └── svc_test.go │ │ └── test2 │ │ └── svc_test.go │ ├── svc3 │ └── svc.go │ └── svc4 │ └── svc.go ├── nano.go ├── nano_interfaces.go └── nano_test.go /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 István Pásztor 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 | -------------------------------------------------------------------------------- /addons/discovery/discoverer.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import "errors" 4 | 5 | // NotFoundError can be returned by returned by Discoverer.Discover when it 6 | // can't locate a given service. 7 | var NotFoundError = errors.New("not found") 8 | 9 | // Discoverer helps locating services. 10 | type Discoverer interface { 11 | // Discover receives the name of a service and returns the "host:port" of 12 | // the service in a format expected by net.Dial. 13 | // 14 | // It should return NotFoundError if the given service was not found but 15 | // returning other errors is also allowed. E.g.: If a network error happens 16 | // during a DNS lookup then returning the network error instead of a 17 | // NotFoundError makes sense. 18 | Discover(name string) (string, error) 19 | } 20 | -------------------------------------------------------------------------------- /addons/discovery/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package discovery provides different ways to resolve the name of your services 3 | to their respective "addr:port". 4 | 5 | Different implementations of the Discoverer interface have been placed to 6 | separate sub-packages intentionally. This way the unused implementations don't 7 | become dependencies of the compiled server executable. 8 | */ 9 | package discovery 10 | -------------------------------------------------------------------------------- /addons/discovery/static/static_discoverer.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import "github.com/pasztorpisti/nano/addons/discovery" 4 | 5 | // Discoverer implements the discovery.Discoverer interface. It can resolve 6 | // a predefined set of service names into predefined net.Dial compatible 7 | // addresses. 8 | type Discoverer map[string]string 9 | 10 | func (v Discoverer) Discover(name string) (string, error) { 11 | if addr, ok := v[name]; ok { 12 | return addr, nil 13 | } 14 | return "", discovery.NotFoundError 15 | } 16 | -------------------------------------------------------------------------------- /addons/log/log_stream.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "io" 4 | 5 | type LogStream interface { 6 | io.Writer 7 | AfterLog() 8 | } 9 | 10 | func NewWriterStream(w io.Writer) LogStream { 11 | return writerStream{w} 12 | } 13 | 14 | type writerStream struct { 15 | io.Writer 16 | } 17 | 18 | func (writerStream) AfterLog() {} 19 | 20 | // TODO: implement a log rotating file stream 21 | -------------------------------------------------------------------------------- /addons/log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "sync" 8 | "time" 9 | 10 | "github.com/pasztorpisti/nano" 11 | ) 12 | 13 | type Map map[string]interface{} 14 | 15 | type Logger interface { 16 | Info(c *nano.Ctx, msg string) 17 | Infom(c *nano.Ctx, msg string, m Map) 18 | Infof(c *nano.Ctx, format string, a ...interface{}) 19 | Err(c *nano.Ctx, err error, msg string) 20 | Errm(c *nano.Ctx, err error, msg string, m Map) 21 | Errf(c *nano.Ctx, err error, format string, a ...interface{}) 22 | } 23 | 24 | var Default Logger = NewLogger(NewWriterStream(os.Stderr)) 25 | 26 | func Info(c *nano.Ctx, msg string) { 27 | Default.Info(c, msg) 28 | } 29 | 30 | func Infom(c *nano.Ctx, msg string, m Map) { 31 | Default.Infom(c, msg, m) 32 | } 33 | 34 | func Infof(c *nano.Ctx, format string, a ...interface{}) { 35 | Default.Infof(c, format, a...) 36 | } 37 | 38 | func Err(c *nano.Ctx, err error, msg string) { 39 | Default.Err(c, err, msg) 40 | } 41 | 42 | func Errm(c *nano.Ctx, err error, msg string, m Map) { 43 | Default.Errm(c, err, msg, m) 44 | } 45 | 46 | func Errf(c *nano.Ctx, err error, format string, a ...interface{}) { 47 | Default.Errf(c, err, format, a...) 48 | } 49 | 50 | func NewLogger(s LogStream) Logger { 51 | return &logger{s: s} 52 | } 53 | 54 | type logger struct { 55 | mu sync.Mutex 56 | s LogStream 57 | } 58 | 59 | const ( 60 | typeInfo = "INFO" 61 | typeError = "ERROR" 62 | ) 63 | 64 | func (p *logger) log(c *nano.Ctx, logType, msg string) { 65 | t := time.Now().UTC().Format(time.RFC3339) 66 | clientName := "-" 67 | svcName := "-" 68 | reqID := "-" 69 | if c != nil { 70 | if c.ClientName != "" { 71 | clientName = c.ClientName 72 | } 73 | if c.Svc != nil { 74 | svcName = c.Svc.Name() 75 | } 76 | if c.ReqID != "" { 77 | reqID = c.ReqID 78 | } 79 | } 80 | p.mu.Lock() 81 | defer p.mu.Unlock() 82 | fmt.Fprintf(p.s, "%s | %s | %s | %s -> %s | %s\n", 83 | t, logType, reqID, clientName, svcName, msg) 84 | p.s.AfterLog() 85 | } 86 | 87 | func (p *logger) Info(c *nano.Ctx, msg string) { 88 | p.log(c, typeInfo, msg) 89 | } 90 | 91 | func (p *logger) Infom(c *nano.Ctx, msg string, m Map) { 92 | b := bytes.NewBuffer(nil) 93 | for k, v := range m { 94 | fmt.Fprintf(b, " %s=%#v", k, v) 95 | } 96 | p.log(c, typeInfo, msg+b.String()) 97 | } 98 | 99 | func (p *logger) Infof(c *nano.Ctx, format string, a ...interface{}) { 100 | p.log(c, typeInfo, fmt.Sprintf(format, a...)) 101 | } 102 | 103 | func (p *logger) Err(c *nano.Ctx, err error, msg string) { 104 | p.Errm(c, err, msg, nil) 105 | } 106 | 107 | func (p *logger) Errm(c *nano.Ctx, err error, msg string, m Map) { 108 | if err == nil && len(m) == 0 { 109 | p.log(c, typeError, msg) 110 | return 111 | } 112 | 113 | b := bytes.NewBuffer(nil) 114 | if err != nil { 115 | fmt.Fprintf(b, " error=%v", err) 116 | } 117 | for k, v := range m { 118 | fmt.Fprintf(b, " %s=%#v", k, v) 119 | } 120 | p.log(c, typeError, msg+b.String()) 121 | } 122 | 123 | func (p *logger) Errf(c *nano.Ctx, err error, format string, a ...interface{}) { 124 | Default.Errm(c, err, fmt.Sprintf(format, a...), nil) 125 | } 126 | -------------------------------------------------------------------------------- /addons/transport/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | "reflect" 8 | 9 | "github.com/pasztorpisti/nano" 10 | "github.com/pasztorpisti/nano/addons/discovery" 11 | "github.com/pasztorpisti/nano/addons/transport/http/config" 12 | "github.com/pasztorpisti/nano/addons/transport/http/serialization" 13 | "github.com/pasztorpisti/nano/addons/util" 14 | ) 15 | 16 | type ClientOptions struct { 17 | Client *http.Client 18 | Discoverer discovery.Discoverer 19 | Serializer *serialization.ClientSideSerializer 20 | PrefixURLPath bool 21 | } 22 | 23 | // DefaultClientOptions is used by NewClient when its opts parameter is nil. 24 | // If DefaultClientOptions is nil then the opts parameter of NewClient can't be nil. 25 | var DefaultClientOptions *ClientOptions 26 | 27 | func NewClient(opts *ClientOptions, cfg *config.ServiceConfig) nano.Service { 28 | if opts == nil { 29 | if DefaultClientOptions == nil { 30 | panic("both opts and DefaultClientOptions are nil") 31 | } 32 | opts = DefaultClientOptions 33 | } 34 | 35 | endpoints := make(map[reflect.Type]*config.EndpointConfig, len(cfg.Endpoints)) 36 | for _, ep := range cfg.Endpoints { 37 | if _, ok := endpoints[ep.ReqType]; ok { 38 | panic("multiple endpoints have the same req type: " + ep.ReqType.String()) 39 | } 40 | endpoints[ep.ReqType] = ep 41 | } 42 | 43 | return &client{ 44 | svcName: cfg.ServiceName, 45 | endpoints: endpoints, 46 | opts: opts, 47 | } 48 | } 49 | 50 | func (p *ClientOptions) client() *http.Client { 51 | if p.Client == nil { 52 | return http.DefaultClient 53 | } 54 | return p.Client 55 | } 56 | 57 | // client implements the nano.Service interface. 58 | type client struct { 59 | svcName string 60 | endpoints map[reflect.Type]*config.EndpointConfig 61 | opts *ClientOptions 62 | } 63 | 64 | func (p *client) Name() string { 65 | return p.svcName 66 | } 67 | 68 | func (p *client) Init(cs nano.ClientSet) error { 69 | return nil 70 | } 71 | 72 | func (p *client) Handle(c *nano.Ctx, req interface{}) (resp interface{}, err error) { 73 | reqType := reflect.TypeOf(req) 74 | if reqType.Kind() != reflect.Ptr { 75 | return nil, p.Err(nil, "expected a pointer request type, got "+reqType.String()) 76 | } 77 | ec, ok := p.endpoints[reqType.Elem()] 78 | if !ok { 79 | return nil, p.Err(nil, "can't handle request type "+reqType.String()) 80 | } 81 | 82 | addr, err := p.opts.Discoverer.Discover(p.svcName) 83 | if err != nil { 84 | return nil, err 85 | } 86 | url := "http://" + addr 87 | if p.opts.PrefixURLPath { 88 | url += "/" + p.svcName 89 | } 90 | url += ec.Path 91 | 92 | var reqBody io.Reader 93 | var header http.Header 94 | h, body, err := p.opts.Serializer.SerializeRequest(ec, c, req) 95 | if err != nil { 96 | return nil, p.Err(err, "error serializing request") 97 | } 98 | reqBody = bytes.NewReader(body) 99 | header = h 100 | 101 | httpReq, err := http.NewRequest(ec.Method, url, reqBody) 102 | if err != nil { 103 | return nil, p.Err(err, "error creating request") 104 | } 105 | httpReq.Header = header 106 | 107 | httpResp, err := p.opts.client().Do(httpReq) 108 | if err != nil { 109 | return nil, p.Err(err, "http request failure") 110 | } 111 | defer httpResp.Body.Close() 112 | 113 | respObj, respErr, err := p.opts.Serializer.DeserializeResponse(ec, c, httpResp) 114 | if err != nil { 115 | return nil, p.Err(err, "error deserializing response") 116 | } 117 | return respObj, respErr 118 | } 119 | 120 | func (p *client) Err(cause error, msg string) error { 121 | return util.Err(cause, "service "+p.svcName+": "+msg) 122 | } 123 | -------------------------------------------------------------------------------- /addons/transport/http/client_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "net/url" 10 | "reflect" 11 | "testing" 12 | 13 | "github.com/pasztorpisti/nano" 14 | "github.com/pasztorpisti/nano/addons/discovery/static" 15 | "github.com/pasztorpisti/nano/addons/transport/http/config" 16 | json_ser "github.com/pasztorpisti/nano/addons/transport/http/serialization/json" 17 | "github.com/pasztorpisti/nano/addons/util" 18 | ) 19 | 20 | const ( 21 | clientSVCName = "test_svc" 22 | clientTestReqID = "TestReqID" 23 | clientTestClientName = "test" 24 | clientJSONContentType = "application/json; charset=utf-8" 25 | ) 26 | 27 | type ClientReq struct { 28 | S string 29 | I int 30 | } 31 | 32 | type ClientResp struct { 33 | B0 bool 34 | B1 bool 35 | } 36 | 37 | type ClientGetReq struct { 38 | } 39 | 40 | type ClientGetResp struct { 41 | S string 42 | } 43 | 44 | type ClientGetDirReq struct { 45 | } 46 | 47 | var clientCFG = &config.ServiceConfig{ 48 | ServiceName: clientSVCName, 49 | Endpoints: []*config.EndpointConfig{ 50 | { 51 | Method: "POST", 52 | Path: "/", 53 | HasReqContent: true, 54 | ReqType: reflect.TypeOf((*ClientReq)(nil)).Elem(), 55 | RespType: reflect.TypeOf((*ClientResp)(nil)).Elem(), 56 | }, 57 | { 58 | Method: "GET", 59 | Path: "/", 60 | HasReqContent: false, 61 | ReqType: reflect.TypeOf((*ClientGetReq)(nil)).Elem(), 62 | RespType: reflect.TypeOf((*ClientGetResp)(nil)).Elem(), 63 | }, 64 | { 65 | Method: "GET", 66 | Path: "/dir", 67 | HasReqContent: false, 68 | ReqType: reflect.TypeOf((*ClientGetDirReq)(nil)).Elem(), 69 | RespType: nil, 70 | }, 71 | }, 72 | } 73 | 74 | func newClient(prefixURLPath bool, h http.HandlerFunc) (client nano.Service, cleanup func()) { 75 | server := httptest.NewServer(h) 76 | 77 | // transport that routes all traffic to the test server 78 | transport := &http.Transport{ 79 | Proxy: func(req *http.Request) (*url.URL, error) { 80 | return url.Parse(server.URL) 81 | }, 82 | } 83 | 84 | opts := &ClientOptions{ 85 | Client: &http.Client{Transport: transport}, 86 | Discoverer: static.Discoverer{clientSVCName: clientSVCName + ":8000"}, 87 | Serializer: json_ser.ClientSideSerializer, 88 | PrefixURLPath: prefixURLPath, 89 | } 90 | 91 | client = NewClient(opts, clientCFG) 92 | cleanup = server.Close 93 | return 94 | } 95 | 96 | func newCtx(svc nano.Service) *nano.Ctx { 97 | return &nano.Ctx{ 98 | ReqID: clientTestReqID, 99 | Context: context.Background(), 100 | Svc: svc, 101 | ClientName: clientTestClientName, 102 | } 103 | } 104 | 105 | func TestClient_Req(t *testing.T) { 106 | called := false 107 | client, cleanup := newClient(true, func(w http.ResponseWriter, r *http.Request) { 108 | called = true 109 | if path, want := r.URL.Path, "/"+clientSVCName+"/"; path != want { 110 | t.Errorf("req path == %q, want %q", path, want) 111 | } 112 | if v := r.Header.Get("Content-Type"); v != clientJSONContentType { 113 | t.Errorf("req Content-Type == %q, want %q", v, clientJSONContentType) 114 | } 115 | if v := r.Header.Get(json_ser.HeaderReqID); v != clientTestReqID { 116 | t.Errorf("req id == %q, want %q", v, clientTestReqID) 117 | } 118 | if v := r.Header.Get(json_ser.HeaderClientName); v != clientTestClientName { 119 | t.Errorf("req client name == %q, want %q", v, clientTestClientName) 120 | } 121 | 122 | reqBody, err := ioutil.ReadAll(r.Body) 123 | if err != nil { 124 | t.Errorf("error reading request body: %v", err) 125 | return 126 | } 127 | 128 | req := make(map[string]interface{}) 129 | err = json.Unmarshal(reqBody, &req) 130 | if err != nil { 131 | t.Errorf("error unmarshaling request :: %v", err) 132 | } else if len(req) != 2 || req["S"] != "str" || req["I"] != 42.0 { 133 | t.Errorf("wrong req json: %q", string(reqBody)) 134 | } 135 | 136 | respBody, err := json.Marshal(&ClientResp{ 137 | B0: false, 138 | B1: true, 139 | }) 140 | if err != nil { 141 | t.Errorf("error marshaling response :: %v", err) 142 | return 143 | } 144 | w.Header().Set("Content-Type", clientJSONContentType) 145 | w.WriteHeader(200) 146 | _, err = w.Write(respBody) 147 | if err != nil { 148 | t.Errorf("error writing response :: %v", err) 149 | } 150 | }) 151 | defer cleanup() 152 | 153 | c := newCtx(client) 154 | req := &ClientReq{ 155 | S: "str", 156 | I: 42, 157 | } 158 | resp, err := client.Handle(c, req) 159 | if err != nil { 160 | t.Errorf("client error :: %v", err) 161 | t.FailNow() 162 | } 163 | if !called { 164 | t.Error("handler wasn't called") 165 | } 166 | 167 | respObj, ok := resp.(*ClientResp) 168 | if !ok { 169 | t.Errorf("client returned response object of type %T, want %T", resp, respObj) 170 | t.FailNow() 171 | } 172 | 173 | if respObj.B0 || !respObj.B1 { 174 | t.Errorf("wrong response object value: %#v", respObj) 175 | } 176 | } 177 | 178 | func TestClient_GetReq(t *testing.T) { 179 | called := false 180 | client, cleanup := newClient(true, func(w http.ResponseWriter, r *http.Request) { 181 | called = true 182 | if path, want := r.URL.Path, "/"+clientSVCName+"/"; path != want { 183 | t.Errorf("req path == %q, want %q", path, want) 184 | } 185 | if v := r.Header.Get("Content-Type"); v != "" { 186 | t.Errorf("unexpected Content-Type header: %q", v) 187 | } 188 | if v := r.Header.Get(json_ser.HeaderReqID); v != clientTestReqID { 189 | t.Errorf("req id == %q, want %q", v, clientTestReqID) 190 | } 191 | if v := r.Header.Get(json_ser.HeaderClientName); v != clientTestClientName { 192 | t.Errorf("req client name == %q, want %q", v, clientTestClientName) 193 | } 194 | 195 | reqBody, err := ioutil.ReadAll(r.Body) 196 | if err != nil { 197 | t.Errorf("error reading request body: %v", err) 198 | return 199 | } 200 | 201 | if len(reqBody) != 0 { 202 | t.Error("unexpected request body") 203 | } 204 | 205 | respBody, err := json.Marshal(&ClientGetResp{ 206 | S: "str", 207 | }) 208 | if err != nil { 209 | t.Errorf("error marshaling response :: %v", err) 210 | return 211 | } 212 | w.Header().Set("Content-Type", clientJSONContentType) 213 | w.WriteHeader(200) 214 | _, err = w.Write(respBody) 215 | if err != nil { 216 | t.Errorf("error writing response :: %v", err) 217 | } 218 | }) 219 | defer cleanup() 220 | 221 | c := newCtx(client) 222 | resp, err := client.Handle(c, &ClientGetReq{}) 223 | if err != nil { 224 | t.Errorf("client error :: %v", err) 225 | t.FailNow() 226 | } 227 | if !called { 228 | t.Error("handler wasn't called") 229 | } 230 | 231 | respObj, ok := resp.(*ClientGetResp) 232 | if !ok { 233 | t.Errorf("client returned response object of type %T, want %T", resp, respObj) 234 | t.FailNow() 235 | } 236 | 237 | if respObj.S != "str" { 238 | t.Errorf("wrong response object value: %#v", respObj) 239 | } 240 | } 241 | 242 | func TestClient_GetDirReq(t *testing.T) { 243 | called := false 244 | client, cleanup := newClient(true, func(w http.ResponseWriter, r *http.Request) { 245 | called = true 246 | if path, want := r.URL.Path, "/"+clientSVCName+"/dir"; path != want { 247 | t.Errorf("req path == %q, want %q", path, want) 248 | } 249 | if v := r.Header.Get("Content-Type"); v != "" { 250 | t.Errorf("unexpected Content-Type header: %q", v) 251 | } 252 | if v := r.Header.Get(json_ser.HeaderReqID); v != clientTestReqID { 253 | t.Errorf("req id == %q, want %q", v, clientTestReqID) 254 | } 255 | if v := r.Header.Get(json_ser.HeaderClientName); v != clientTestClientName { 256 | t.Errorf("req client name == %q, want %q", v, clientTestClientName) 257 | } 258 | 259 | reqBody, err := ioutil.ReadAll(r.Body) 260 | if err != nil { 261 | t.Errorf("error reading request body: %v", err) 262 | return 263 | } 264 | 265 | if len(reqBody) != 0 { 266 | t.Error("unexpected request body") 267 | } 268 | }) 269 | defer cleanup() 270 | 271 | c := newCtx(client) 272 | resp, err := client.Handle(c, &ClientGetDirReq{}) 273 | if err != nil { 274 | t.Errorf("client error :: %v", err) 275 | t.FailNow() 276 | } 277 | if !called { 278 | t.Error("handler wasn't called") 279 | } 280 | 281 | if resp != nil { 282 | t.Errorf("resp == %v, want nil", resp) 283 | } 284 | } 285 | 286 | func testClient_ErrorResponse(t *testing.T, errCode string) { 287 | const errMsg = "test error" 288 | called := false 289 | client, cleanup := newClient(true, func(w http.ResponseWriter, r *http.Request) { 290 | called = true 291 | if path, want := r.URL.Path, "/"+clientSVCName+"/dir"; path != want { 292 | t.Errorf("req path == %q, want %q", path, want) 293 | } 294 | if v := r.Header.Get("Content-Type"); v != "" { 295 | t.Errorf("unexpected Content-Type header: %q", v) 296 | } 297 | if v := r.Header.Get(json_ser.HeaderReqID); v != clientTestReqID { 298 | t.Errorf("req id == %q, want %q", v, clientTestReqID) 299 | } 300 | if v := r.Header.Get(json_ser.HeaderClientName); v != clientTestClientName { 301 | t.Errorf("req client name == %q, want %q", v, clientTestClientName) 302 | } 303 | 304 | reqBody, err := ioutil.ReadAll(r.Body) 305 | if err != nil { 306 | t.Errorf("error reading request body: %v", err) 307 | return 308 | } 309 | 310 | if len(reqBody) != 0 { 311 | t.Error("unexpected request body") 312 | } 313 | 314 | respBody, err := json.Marshal(&json_ser.ErrorResponse{ 315 | Code: errCode, 316 | Msg: errMsg, 317 | }) 318 | if err != nil { 319 | t.Errorf("error marshaling response :: %v", err) 320 | return 321 | } 322 | w.Header().Set("Content-Type", clientJSONContentType) 323 | w.WriteHeader(config.ErrorCodeToHTTPStatus(errCode)) 324 | _, err = w.Write(respBody) 325 | if err != nil { 326 | t.Errorf("error writing response :: %v", err) 327 | } 328 | }) 329 | defer cleanup() 330 | 331 | c := newCtx(client) 332 | _, err := client.Handle(c, &ClientGetDirReq{}) 333 | if err == nil { 334 | t.Error("unexpected success") 335 | t.FailNow() 336 | } 337 | 338 | if !called { 339 | t.Error("handler wasn't called") 340 | } 341 | 342 | if v := util.GetErrCode(err); v != errCode { 343 | t.Errorf("error code == %q, want %q", v, errCode) 344 | } 345 | if err.Error() != errMsg { 346 | t.Errorf("error msg == %q, want %q", err.Error(), errMsg) 347 | } 348 | } 349 | 350 | func TestClient_GenericErrorResponse(t *testing.T) { 351 | testClient_ErrorResponse(t, "") 352 | } 353 | 354 | func TestClient_NotFoundErrorResponse(t *testing.T) { 355 | testClient_ErrorResponse(t, config.ErrorCodeNotFound) 356 | } 357 | 358 | func TestClient_ClientErrorResponse(t *testing.T) { 359 | testClient_ErrorResponse(t, "C-MYERROR") 360 | } 361 | 362 | func TestClient_ServerErrorResponse(t *testing.T) { 363 | testClient_ErrorResponse(t, "S-MYERROR") 364 | } 365 | -------------------------------------------------------------------------------- /addons/transport/http/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "reflect" 4 | 5 | type ServiceConfig struct { 6 | ServiceName string 7 | Endpoints []*EndpointConfig 8 | } 9 | 10 | type EndpointConfig struct { 11 | Method string 12 | Path string 13 | HasReqContent bool 14 | ReqType reflect.Type 15 | RespType reflect.Type 16 | } 17 | -------------------------------------------------------------------------------- /addons/transport/http/config/error_codes.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "strings" 4 | 5 | const ( 6 | ErrorCodeBadRequest = "C-BAD-REQUEST" 7 | ErrorCodeNotFound = "C-NOT-FOUND" 8 | ErrorCodeBadRequestContentType = "C-BAD-CONTENT-TYPE" 9 | 10 | ErrorCodeServerError = "S-ERROR" 11 | 12 | ClientErrorCodePrefix = "C-" 13 | ServerErrorCodePrefix = "S-" 14 | ) 15 | 16 | var codeToStatus = map[string]int{ 17 | ErrorCodeBadRequest: 400, 18 | ErrorCodeNotFound: 404, 19 | ErrorCodeBadRequestContentType: 415, 20 | } 21 | 22 | var ErrorCodeToHTTPStatus = func(code string) int { 23 | if status, ok := codeToStatus[code]; ok { 24 | return status 25 | } 26 | if strings.HasPrefix(code, ClientErrorCodePrefix) { 27 | return 400 28 | } 29 | return 500 30 | } 31 | -------------------------------------------------------------------------------- /addons/transport/http/config/gen_http_transport_config/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "text/template" 9 | ) 10 | 11 | const helpText = ` 12 | Usage: go run gen_http_transport_config/main.go mapping [mapping [mapping [...]]] 13 | 14 | mapping has the following format: 15 | input_json_file_path:output_go_file_path 16 | 17 | Example: 18 | 19 | go run gen_http_transport_config/main.go my/api/transport.json:my/api_go/transport.go 20 | ` 21 | 22 | func helpExit(errorMsg string) { 23 | if errorMsg != "" { 24 | fmt.Fprintln(os.Stderr, errorMsg) 25 | } 26 | fmt.Print(helpText) 27 | os.Exit(1) 28 | } 29 | 30 | func main() { 31 | mappings := os.Args[1:] 32 | if len(mappings) == 0 { 33 | helpExit("") 34 | } 35 | 36 | var pairs [][]string 37 | for _, m := range mappings { 38 | pair := strings.Split(m, ":") 39 | if len(pair) != 2 { 40 | helpExit("Invalid mapping: " + m) 41 | } 42 | pairs = append(pairs, pair) 43 | } 44 | 45 | var err error 46 | tpl, err = template.New("go").Parse(tplStr) 47 | if err != nil { 48 | helpExit("Error parsing template.") 49 | } 50 | 51 | errors := 0 52 | for _, pair := range pairs { 53 | if !generate(pair[0], pair[1]) { 54 | errors++ 55 | } 56 | } 57 | 58 | if errors > 0 { 59 | fmt.Printf("ERRORS: %d\n", errors) 60 | os.Exit(1) 61 | } 62 | } 63 | 64 | func generate(inputJSONPath, outputGoPath string) bool { 65 | f, err := os.Open(inputJSONPath) 66 | if err != nil { 67 | fmt.Fprintf(os.Stderr, "Error opening %q: %v\n", inputJSONPath, err) 68 | return false 69 | } 70 | defer f.Close() 71 | 72 | sc := new(ServiceConfig) 73 | err = json.NewDecoder(f).Decode(sc) 74 | if err != nil { 75 | fmt.Fprintf(os.Stderr, "Error decoding json file %q: %v\n", inputJSONPath, err) 76 | return false 77 | } 78 | 79 | f2, err := os.Create(outputGoPath) 80 | if err != nil { 81 | fmt.Fprintf(os.Stderr, "Error creating file: %v", err) 82 | return false 83 | } 84 | defer f2.Close() 85 | 86 | err = tpl.Execute(f2, sc) 87 | if err != nil { 88 | os.Remove(outputGoPath) 89 | fmt.Fprintf(os.Stderr, "Template execution error: %v", err) 90 | return false 91 | } 92 | 93 | return true 94 | } 95 | 96 | type ServiceConfig struct { 97 | ServiceName string `json:"service_name"` 98 | Endpoints []*EndpointConfig `json:"endpoints"` 99 | } 100 | 101 | type EndpointConfig struct { 102 | Method string `json:"method"` 103 | Path string `json:"path"` 104 | HasReqContent bool `json:"has_req_content"` 105 | ReqType string `json:"req_type"` 106 | RespType string `json:"resp_type"` 107 | } 108 | 109 | var tpl *template.Template 110 | 111 | const tplStr = `/* 112 | DO NOT EDIT! 113 | This file has been generated from JSON by gen_http_transport_config. 114 | */ 115 | package {{ .ServiceName }} 116 | 117 | import ( 118 | "reflect" 119 | 120 | "github.com/pasztorpisti/nano/addons/transport/http/config" 121 | ) 122 | 123 | var HTTPTransportConfig = &config.ServiceConfig{ 124 | ServiceName: {{ printf "%q" .ServiceName }}, 125 | Endpoints: []*config.EndpointConfig{ 126 | {{- range $i, $ep := .Endpoints }} 127 | { 128 | Method: {{ printf "%q" $ep.Method }}, 129 | Path: {{ printf "%q" $ep.Path }}, 130 | HasReqContent: {{ $ep.HasReqContent }}, 131 | ReqType: reflect.TypeOf((*{{ $ep.ReqType }})(nil)).Elem(), 132 | RespType: reflect.TypeOf((*{{ $ep.RespType }})(nil)).Elem(), 133 | }, 134 | {{- end }} 135 | }, 136 | } 137 | ` 138 | -------------------------------------------------------------------------------- /addons/transport/http/listener.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | 8 | "github.com/julienschmidt/httprouter" 9 | "github.com/pasztorpisti/nano" 10 | "github.com/pasztorpisti/nano/addons/log" 11 | "github.com/pasztorpisti/nano/addons/transport/http/config" 12 | "github.com/pasztorpisti/nano/addons/transport/http/serialization" 13 | "github.com/pasztorpisti/nano/addons/util" 14 | ) 15 | 16 | type ListenerOptions struct { 17 | BindAddr string 18 | Serializer *serialization.ServerSideSerializer 19 | PrefixURLPath bool 20 | } 21 | 22 | var DefaultListenerOptions *ListenerOptions 23 | 24 | func NewListener(opts *ListenerOptions, cfgs ...*config.ServiceConfig) nano.Listener { 25 | if opts == nil { 26 | if DefaultListenerOptions == nil { 27 | panic("both opts and DefaultListenerOptions are nil") 28 | } 29 | opts = DefaultListenerOptions 30 | } 31 | return &listener{ 32 | cfgs: cfgs, 33 | opts: opts, 34 | } 35 | } 36 | 37 | type listener struct { 38 | cfgs []*config.ServiceConfig 39 | opts *ListenerOptions 40 | router *httprouter.Router 41 | } 42 | 43 | func (p *listener) Init(srv nano.ServiceSet) error { 44 | p.router = httprouter.New() 45 | p.router.GET("/health-check", func(http.ResponseWriter, *http.Request, httprouter.Params) {}) 46 | 47 | duplicateCheck := map[string]struct{}{} 48 | for _, cfg := range p.cfgs { 49 | svc, err := srv.LookupService(cfg.ServiceName) 50 | if err != nil { 51 | return util.Err(err, "listener couldn't lookup a service") 52 | } 53 | 54 | for _, ec := range cfg.Endpoints { 55 | path := ec.Path 56 | if p.opts.PrefixURLPath { 57 | path = "/" + cfg.ServiceName + path 58 | } 59 | id := ec.Method + " " + path 60 | if _, ok := duplicateCheck[id]; ok { 61 | return fmt.Errorf("service %v: duplicate endpoint: %v", 62 | cfg.ServiceName, id) 63 | } 64 | duplicateCheck[id] = struct{}{} 65 | 66 | ep := &endpoint{ 67 | cfg: ec, 68 | svc: svc, 69 | Serializer: p.opts.Serializer, 70 | } 71 | p.router.Handle(ec.Method, path, ep.Handler) 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (p *listener) Listen() error { 79 | return http.ListenAndServe(p.opts.BindAddr, p.router) 80 | } 81 | 82 | type endpoint struct { 83 | cfg *config.EndpointConfig 84 | svc nano.Service 85 | Serializer *serialization.ServerSideSerializer 86 | } 87 | 88 | func (p *endpoint) Handler(w http.ResponseWriter, r *http.Request, 89 | rp httprouter.Params) { 90 | req, ri, err := p.Serializer.DeserializeRequest(p.cfg, r) 91 | if err != nil { 92 | log.Err(nil, err, "error deserialising request") 93 | err = p.Serializer.SerializeResponse(p.cfg, nil, w, r, nil, err) 94 | if err != nil { 95 | log.Err(nil, err, "error serialising error response") 96 | } 97 | return 98 | } 99 | 100 | client := nano.NewClient(p.svc, ri.ClientName) 101 | 102 | c := &nano.Ctx{ 103 | ReqID: ri.ReqID, 104 | } 105 | resp, err := client.Request(c, req) 106 | 107 | if err != nil { 108 | resp = nil 109 | } else { 110 | expectedType := p.cfg.RespType 111 | if expectedType != nil { 112 | expectedType = reflect.PtrTo(expectedType) 113 | } 114 | if reflect.TypeOf(resp) != expectedType { 115 | // this is a programming error in the service 116 | log.Errf(c, err, "service returned an object of type %v, want %v", 117 | reflect.TypeOf(resp), expectedType) 118 | err = p.Serializer.SerializeResponse(p.cfg, c, w, r, nil, serverError) 119 | if err != nil { 120 | log.Err(c, err, "error serialising error response") 121 | } 122 | return 123 | } 124 | } 125 | 126 | err = p.Serializer.SerializeResponse(p.cfg, c, w, r, resp, err) 127 | if err != nil { 128 | log.Err(c, err, "error serialising response") 129 | return 130 | } 131 | } 132 | 133 | var serverError = util.ErrCode(nil, config.ErrorCodeServerError, 134 | "Internal Server Error") 135 | -------------------------------------------------------------------------------- /addons/transport/http/listener_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/http/httptest" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/pasztorpisti/nano" 12 | "github.com/pasztorpisti/nano/addons/transport/http/config" 13 | json_ser "github.com/pasztorpisti/nano/addons/transport/http/serialization/json" 14 | "github.com/pasztorpisti/nano/addons/util" 15 | ) 16 | 17 | const ( 18 | listenSVCName = "test_svc" 19 | listenTestReqID = "TestReqID" 20 | listenTestClientName = "test_client" 21 | listenJSONContentType = "application/json; charset=utf-8" 22 | ) 23 | 24 | type ListenReq struct { 25 | S string 26 | I int 27 | } 28 | 29 | type ListenResp struct { 30 | B0 bool 31 | B1 bool 32 | } 33 | 34 | type ListenGetReq struct { 35 | } 36 | 37 | type ListenGetResp struct { 38 | S string 39 | } 40 | 41 | type ListenGetDirReq struct { 42 | } 43 | 44 | var listenCFG = &config.ServiceConfig{ 45 | ServiceName: listenSVCName, 46 | Endpoints: []*config.EndpointConfig{ 47 | { 48 | Method: "POST", 49 | Path: "/", 50 | HasReqContent: true, 51 | ReqType: reflect.TypeOf((*ListenReq)(nil)).Elem(), 52 | RespType: reflect.TypeOf((*ListenResp)(nil)).Elem(), 53 | }, 54 | { 55 | Method: "GET", 56 | Path: "/", 57 | HasReqContent: false, 58 | ReqType: reflect.TypeOf((*ListenGetReq)(nil)).Elem(), 59 | RespType: reflect.TypeOf((*ListenGetResp)(nil)).Elem(), 60 | }, 61 | { 62 | Method: "GET", 63 | Path: "/dir", 64 | HasReqContent: false, 65 | ReqType: reflect.TypeOf((*ListenGetDirReq)(nil)).Elem(), 66 | RespType: nil, 67 | }, 68 | }, 69 | } 70 | 71 | func newListener(prefixURLPath bool, h util.HandlerFunc) http.Handler { 72 | l := NewListener(&ListenerOptions{ 73 | Serializer: json_ser.ServerSideSerializer, 74 | PrefixURLPath: prefixURLPath, 75 | }, listenCFG) 76 | 77 | svc := util.NewService(listenSVCName, h) 78 | ss := nano.NewServiceSet(svc) 79 | 80 | err := l.Init(ss) 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | return l.(*listener).router 86 | } 87 | 88 | func TestListen_Req(t *testing.T) { 89 | called := false 90 | var rc *nano.Ctx 91 | var ro interface{} 92 | h := newListener(true, func(c *nano.Ctx, req interface{}) (interface{}, error) { 93 | called = true 94 | rc = c 95 | ro = req 96 | return &ListenResp{ 97 | B0: false, 98 | B1: true, 99 | }, nil 100 | }) 101 | 102 | reqBody := `{"S":"str","I":42}` 103 | req := httptest.NewRequest("POST", "/"+listenSVCName+"/", strings.NewReader(reqBody)) 104 | req.Header.Set("Content-Type", listenJSONContentType) 105 | req.Header.Set(json_ser.HeaderReqID, listenTestReqID) 106 | req.Header.Set(json_ser.HeaderClientName, listenTestClientName) 107 | 108 | resp := httptest.NewRecorder() 109 | h.ServeHTTP(resp, req) 110 | 111 | if resp.Code != 200 { 112 | t.Errorf("resp.Code == %v, want %v", resp.Code, 200) 113 | } 114 | if v := resp.Header().Get("Content-Type"); v != listenJSONContentType { 115 | t.Errorf("Content-Type header == %q, want %q", v, listenJSONContentType) 116 | } 117 | 118 | respObj := new(ListenResp) 119 | err := json.Unmarshal(resp.Body.Bytes(), respObj) 120 | if err != nil { 121 | t.Errorf("error unmarshaling response content :: %v", err) 122 | t.FailNow() 123 | } 124 | if respObj.B0 || !respObj.B1 { 125 | t.Errorf("unexpected response content: %#v", respObj) 126 | } 127 | 128 | if !called { 129 | t.Error("request handler wasn't called") 130 | t.FailNow() 131 | } 132 | if rc == nil { 133 | t.Error("request handler received nil *nano.Ctx") 134 | t.FailNow() 135 | } 136 | if rc.ReqID != listenTestReqID { 137 | t.Errorf("rc.ReqID == %q, want %q", rc.ReqID, listenTestReqID) 138 | } 139 | if rc.ClientName != listenTestClientName { 140 | t.Errorf("rc.ClientName == %q, want %q", rc.ClientName, listenTestClientName) 141 | } 142 | reqObj, ok := ro.(*ListenReq) 143 | if ok { 144 | if reqObj.S != "str" { 145 | t.Errorf("reqObj.S == %q, want %q", reqObj.S, "str") 146 | } 147 | if reqObj.I != 42 { 148 | t.Errorf("reqObj.I == %v, want %v", reqObj.I, 42) 149 | } 150 | } else { 151 | t.Errorf("handle received object of type %T, want %T", ro, reqObj) 152 | } 153 | } 154 | 155 | func TestListen_GetReq(t *testing.T) { 156 | called := false 157 | var rc *nano.Ctx 158 | var ro interface{} 159 | h := newListener(true, func(c *nano.Ctx, req interface{}) (interface{}, error) { 160 | called = true 161 | rc = c 162 | ro = req 163 | return &ListenGetResp{ 164 | S: "str", 165 | }, nil 166 | }) 167 | 168 | req := httptest.NewRequest("GET", "/"+listenSVCName+"/", nil) 169 | req.Header.Set(json_ser.HeaderReqID, listenTestReqID) 170 | req.Header.Set(json_ser.HeaderClientName, listenTestClientName) 171 | 172 | resp := httptest.NewRecorder() 173 | h.ServeHTTP(resp, req) 174 | 175 | if resp.Code != 200 { 176 | t.Errorf("resp.Code == %v, want %v", resp.Code, 200) 177 | } 178 | if v := resp.Header().Get("Content-Type"); v != listenJSONContentType { 179 | t.Errorf("Content-Type header == %q, want %q", v, listenJSONContentType) 180 | } 181 | 182 | respObj := new(ListenGetResp) 183 | err := json.Unmarshal(resp.Body.Bytes(), respObj) 184 | if err != nil { 185 | t.Errorf("error unmarshaling response content :: %v", err) 186 | t.FailNow() 187 | } 188 | if respObj.S != "str" { 189 | t.Errorf("unexpected response content: %#v", respObj) 190 | } 191 | 192 | if !called { 193 | t.Error("request handler wasn't called") 194 | t.FailNow() 195 | } 196 | if rc == nil { 197 | t.Error("request handler received nil *nano.Ctx") 198 | t.FailNow() 199 | } 200 | if rc.ReqID != listenTestReqID { 201 | t.Errorf("rc.ReqID == %q, want %q", rc.ReqID, listenTestReqID) 202 | } 203 | if rc.ClientName != listenTestClientName { 204 | t.Errorf("rc.ClientName == %q, want %q", rc.ClientName, listenTestClientName) 205 | } 206 | reqObj, ok := ro.(*ListenGetReq) 207 | if !ok { 208 | t.Errorf("handle received object of type %T, want %T", ro, reqObj) 209 | } 210 | } 211 | 212 | func TestListen_GetDirReq(t *testing.T) { 213 | called := false 214 | var rc *nano.Ctx 215 | var ro interface{} 216 | h := newListener(true, func(c *nano.Ctx, req interface{}) (interface{}, error) { 217 | called = true 218 | rc = c 219 | ro = req 220 | return nil, nil 221 | }) 222 | 223 | req := httptest.NewRequest("GET", "/"+listenSVCName+"/dir", nil) 224 | req.Header.Set(json_ser.HeaderReqID, listenTestReqID) 225 | req.Header.Set(json_ser.HeaderClientName, listenTestClientName) 226 | 227 | resp := httptest.NewRecorder() 228 | h.ServeHTTP(resp, req) 229 | 230 | if resp.Code != 200 { 231 | t.Errorf("resp.Code == %v, want %v", resp.Code, 200) 232 | } 233 | if v := resp.Header().Get("Content-Type"); v != "" { 234 | t.Errorf("unexpected Content-Type header: %q", v) 235 | } 236 | 237 | if resp.Body.Len() != 0 { 238 | t.Errorf("unexpected response body: %v", resp.Body.Bytes()) 239 | } 240 | 241 | if !called { 242 | t.Error("request handler wasn't called") 243 | t.FailNow() 244 | } 245 | if rc == nil { 246 | t.Error("request handler received nil *nano.Ctx") 247 | t.FailNow() 248 | } 249 | if rc.ReqID != listenTestReqID { 250 | t.Errorf("rc.ReqID == %q, want %q", rc.ReqID, listenTestReqID) 251 | } 252 | if rc.ClientName != listenTestClientName { 253 | t.Errorf("rc.ClientName == %q, want %q", rc.ClientName, listenTestClientName) 254 | } 255 | reqObj, ok := ro.(*ListenGetDirReq) 256 | if !ok { 257 | t.Errorf("handle received object of type %T, want %T", ro, reqObj) 258 | } 259 | } 260 | 261 | func TestListen_WithoutPrefixURLPath(t *testing.T) { 262 | called := false 263 | var rc *nano.Ctx 264 | var ro interface{} 265 | h := newListener(false, func(c *nano.Ctx, req interface{}) (interface{}, error) { 266 | called = true 267 | rc = c 268 | ro = req 269 | return nil, nil 270 | }) 271 | 272 | req := httptest.NewRequest("GET", "/dir", nil) 273 | req.Header.Set(json_ser.HeaderReqID, listenTestReqID) 274 | req.Header.Set(json_ser.HeaderClientName, listenTestClientName) 275 | 276 | resp := httptest.NewRecorder() 277 | h.ServeHTTP(resp, req) 278 | 279 | if resp.Code != 200 { 280 | t.Errorf("resp.Code == %v, want %v", resp.Code, 200) 281 | } 282 | if v := resp.Header().Get("Content-Type"); v != "" { 283 | t.Errorf("unexpected Content-Type header: %q", v) 284 | } 285 | 286 | if resp.Body.Len() != 0 { 287 | t.Errorf("unexpected response body: %v", resp.Body.Bytes()) 288 | } 289 | 290 | if !called { 291 | t.Error("request handler wasn't called") 292 | t.FailNow() 293 | } 294 | if rc == nil { 295 | t.Error("request handler received nil *nano.Ctx") 296 | t.FailNow() 297 | } 298 | if rc.ReqID != listenTestReqID { 299 | t.Errorf("rc.ReqID == %q, want %q", rc.ReqID, listenTestReqID) 300 | } 301 | if rc.ClientName != listenTestClientName { 302 | t.Errorf("rc.ClientName == %q, want %q", rc.ClientName, listenTestClientName) 303 | } 304 | reqObj, ok := ro.(*ListenGetDirReq) 305 | if !ok { 306 | t.Errorf("handle received object of type %T, want %T", ro, reqObj) 307 | } 308 | } 309 | 310 | func TestListen_MethodNotAllowed(t *testing.T) { 311 | h := newListener(true, func(c *nano.Ctx, req interface{}) (interface{}, error) { 312 | t.Error("handler was called") 313 | return nil, nil 314 | }) 315 | 316 | reqBody := `{"S":"str","I":42}` 317 | req := httptest.NewRequest("PATCH", "/"+listenSVCName+"/", strings.NewReader(reqBody)) 318 | req.Header.Set("Content-Type", listenJSONContentType) 319 | req.Header.Set(json_ser.HeaderReqID, listenTestReqID) 320 | req.Header.Set(json_ser.HeaderClientName, listenTestClientName) 321 | 322 | resp := httptest.NewRecorder() 323 | h.ServeHTTP(resp, req) 324 | 325 | if resp.Code != http.StatusMethodNotAllowed { 326 | t.Errorf("resp.Code == %v, want %v", resp.Code, http.StatusMethodNotAllowed) 327 | t.FailNow() 328 | } 329 | } 330 | 331 | func testListen_ErrorResponse(t *testing.T, errCode string, expectedStatus int) { 332 | const errMsg = "test error" 333 | called := false 334 | h := newListener(true, func(c *nano.Ctx, req interface{}) (interface{}, error) { 335 | called = true 336 | return nil, util.ErrCode(nil, errCode, errMsg) 337 | }) 338 | 339 | req := httptest.NewRequest("GET", "/"+listenSVCName+"/", nil) 340 | req.Header.Set(json_ser.HeaderReqID, listenTestReqID) 341 | req.Header.Set(json_ser.HeaderClientName, listenTestClientName) 342 | 343 | resp := httptest.NewRecorder() 344 | h.ServeHTTP(resp, req) 345 | 346 | if !called { 347 | t.Error("handler wasn't called") 348 | } 349 | 350 | if resp.Code != expectedStatus { 351 | t.Errorf("resp.Code == %v, want %v", resp.Code, expectedStatus) 352 | } 353 | if v := resp.Header().Get("Content-Type"); v != listenJSONContentType { 354 | t.Errorf("Content-Type header == %q, want %q", v, listenJSONContentType) 355 | } 356 | 357 | respObj := new(json_ser.ErrorResponse) 358 | err := json.Unmarshal(resp.Body.Bytes(), respObj) 359 | if err != nil { 360 | t.Errorf("error unmarshaling error response content :: %v", err) 361 | t.FailNow() 362 | } 363 | if respObj.Code != errCode || respObj.Msg != errMsg { 364 | t.Errorf("unexpected response content: %#v", respObj) 365 | } 366 | } 367 | 368 | func TestListen_NotFoundErrorResponse(t *testing.T) { 369 | testListen_ErrorResponse(t, config.ErrorCodeNotFound, 404) 370 | } 371 | 372 | func TestListen_ClientErrorResponse(t *testing.T) { 373 | testListen_ErrorResponse(t, "C-MYERROR", 400) 374 | } 375 | 376 | func TestListen_ServerErrorResponse(t *testing.T) { 377 | testListen_ErrorResponse(t, "S-MYERROR", 500) 378 | } 379 | -------------------------------------------------------------------------------- /addons/transport/http/serialization/gogo_proto/Transport.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. 2 | // source: Transport.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package gogo_proto is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | Transport.proto 10 | 11 | It has these top-level messages: 12 | Request 13 | RequestMeta 14 | ErrorResponse 15 | */ 16 | package gogo_proto 17 | 18 | import proto "github.com/gogo/protobuf/proto" 19 | import fmt "fmt" 20 | import math "math" 21 | 22 | import io "io" 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ = proto.Marshal 26 | var _ = fmt.Errorf 27 | var _ = math.Inf 28 | 29 | // This is a compile-time assertion to ensure that this generated file 30 | // is compatible with the proto package it is being compiled against. 31 | // A compilation error at this line likely means your copy of the 32 | // proto package needs to be updated. 33 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 34 | 35 | type Request struct { 36 | Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta" json:"meta,omitempty"` 37 | Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` 38 | } 39 | 40 | func (m *Request) Reset() { *m = Request{} } 41 | func (m *Request) String() string { return proto.CompactTextString(m) } 42 | func (*Request) ProtoMessage() {} 43 | func (*Request) Descriptor() ([]byte, []int) { return fileDescriptorTransport, []int{0} } 44 | 45 | func (m *Request) GetMeta() *RequestMeta { 46 | if m != nil { 47 | return m.Meta 48 | } 49 | return nil 50 | } 51 | 52 | func (m *Request) GetPayload() []byte { 53 | if m != nil { 54 | return m.Payload 55 | } 56 | return nil 57 | } 58 | 59 | type RequestMeta struct { 60 | ReqId string `protobuf:"bytes,1,opt,name=req_id,json=reqId,proto3" json:"req_id,omitempty"` 61 | ClientName string `protobuf:"bytes,3,opt,name=client_name,json=clientName,proto3" json:"client_name,omitempty"` 62 | } 63 | 64 | func (m *RequestMeta) Reset() { *m = RequestMeta{} } 65 | func (m *RequestMeta) String() string { return proto.CompactTextString(m) } 66 | func (*RequestMeta) ProtoMessage() {} 67 | func (*RequestMeta) Descriptor() ([]byte, []int) { return fileDescriptorTransport, []int{1} } 68 | 69 | func (m *RequestMeta) GetReqId() string { 70 | if m != nil { 71 | return m.ReqId 72 | } 73 | return "" 74 | } 75 | 76 | func (m *RequestMeta) GetClientName() string { 77 | if m != nil { 78 | return m.ClientName 79 | } 80 | return "" 81 | } 82 | 83 | type ErrorResponse struct { 84 | Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` 85 | Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` 86 | } 87 | 88 | func (m *ErrorResponse) Reset() { *m = ErrorResponse{} } 89 | func (m *ErrorResponse) String() string { return proto.CompactTextString(m) } 90 | func (*ErrorResponse) ProtoMessage() {} 91 | func (*ErrorResponse) Descriptor() ([]byte, []int) { return fileDescriptorTransport, []int{2} } 92 | 93 | func (m *ErrorResponse) GetCode() string { 94 | if m != nil { 95 | return m.Code 96 | } 97 | return "" 98 | } 99 | 100 | func (m *ErrorResponse) GetMsg() string { 101 | if m != nil { 102 | return m.Msg 103 | } 104 | return "" 105 | } 106 | 107 | func init() { 108 | proto.RegisterType((*Request)(nil), "gogo_proto.Request") 109 | proto.RegisterType((*RequestMeta)(nil), "gogo_proto.RequestMeta") 110 | proto.RegisterType((*ErrorResponse)(nil), "gogo_proto.ErrorResponse") 111 | } 112 | func (m *Request) Marshal() (dAtA []byte, err error) { 113 | size := m.Size() 114 | dAtA = make([]byte, size) 115 | n, err := m.MarshalTo(dAtA) 116 | if err != nil { 117 | return nil, err 118 | } 119 | return dAtA[:n], nil 120 | } 121 | 122 | func (m *Request) MarshalTo(dAtA []byte) (int, error) { 123 | var i int 124 | _ = i 125 | var l int 126 | _ = l 127 | if m.Meta != nil { 128 | dAtA[i] = 0xa 129 | i++ 130 | i = encodeVarintTransport(dAtA, i, uint64(m.Meta.Size())) 131 | n1, err := m.Meta.MarshalTo(dAtA[i:]) 132 | if err != nil { 133 | return 0, err 134 | } 135 | i += n1 136 | } 137 | if len(m.Payload) > 0 { 138 | dAtA[i] = 0x12 139 | i++ 140 | i = encodeVarintTransport(dAtA, i, uint64(len(m.Payload))) 141 | i += copy(dAtA[i:], m.Payload) 142 | } 143 | return i, nil 144 | } 145 | 146 | func (m *RequestMeta) Marshal() (dAtA []byte, err error) { 147 | size := m.Size() 148 | dAtA = make([]byte, size) 149 | n, err := m.MarshalTo(dAtA) 150 | if err != nil { 151 | return nil, err 152 | } 153 | return dAtA[:n], nil 154 | } 155 | 156 | func (m *RequestMeta) MarshalTo(dAtA []byte) (int, error) { 157 | var i int 158 | _ = i 159 | var l int 160 | _ = l 161 | if len(m.ReqId) > 0 { 162 | dAtA[i] = 0xa 163 | i++ 164 | i = encodeVarintTransport(dAtA, i, uint64(len(m.ReqId))) 165 | i += copy(dAtA[i:], m.ReqId) 166 | } 167 | if len(m.ClientName) > 0 { 168 | dAtA[i] = 0x1a 169 | i++ 170 | i = encodeVarintTransport(dAtA, i, uint64(len(m.ClientName))) 171 | i += copy(dAtA[i:], m.ClientName) 172 | } 173 | return i, nil 174 | } 175 | 176 | func (m *ErrorResponse) Marshal() (dAtA []byte, err error) { 177 | size := m.Size() 178 | dAtA = make([]byte, size) 179 | n, err := m.MarshalTo(dAtA) 180 | if err != nil { 181 | return nil, err 182 | } 183 | return dAtA[:n], nil 184 | } 185 | 186 | func (m *ErrorResponse) MarshalTo(dAtA []byte) (int, error) { 187 | var i int 188 | _ = i 189 | var l int 190 | _ = l 191 | if len(m.Code) > 0 { 192 | dAtA[i] = 0xa 193 | i++ 194 | i = encodeVarintTransport(dAtA, i, uint64(len(m.Code))) 195 | i += copy(dAtA[i:], m.Code) 196 | } 197 | if len(m.Msg) > 0 { 198 | dAtA[i] = 0x12 199 | i++ 200 | i = encodeVarintTransport(dAtA, i, uint64(len(m.Msg))) 201 | i += copy(dAtA[i:], m.Msg) 202 | } 203 | return i, nil 204 | } 205 | 206 | func encodeFixed64Transport(dAtA []byte, offset int, v uint64) int { 207 | dAtA[offset] = uint8(v) 208 | dAtA[offset+1] = uint8(v >> 8) 209 | dAtA[offset+2] = uint8(v >> 16) 210 | dAtA[offset+3] = uint8(v >> 24) 211 | dAtA[offset+4] = uint8(v >> 32) 212 | dAtA[offset+5] = uint8(v >> 40) 213 | dAtA[offset+6] = uint8(v >> 48) 214 | dAtA[offset+7] = uint8(v >> 56) 215 | return offset + 8 216 | } 217 | func encodeFixed32Transport(dAtA []byte, offset int, v uint32) int { 218 | dAtA[offset] = uint8(v) 219 | dAtA[offset+1] = uint8(v >> 8) 220 | dAtA[offset+2] = uint8(v >> 16) 221 | dAtA[offset+3] = uint8(v >> 24) 222 | return offset + 4 223 | } 224 | func encodeVarintTransport(dAtA []byte, offset int, v uint64) int { 225 | for v >= 1<<7 { 226 | dAtA[offset] = uint8(v&0x7f | 0x80) 227 | v >>= 7 228 | offset++ 229 | } 230 | dAtA[offset] = uint8(v) 231 | return offset + 1 232 | } 233 | func (m *Request) Size() (n int) { 234 | var l int 235 | _ = l 236 | if m.Meta != nil { 237 | l = m.Meta.Size() 238 | n += 1 + l + sovTransport(uint64(l)) 239 | } 240 | l = len(m.Payload) 241 | if l > 0 { 242 | n += 1 + l + sovTransport(uint64(l)) 243 | } 244 | return n 245 | } 246 | 247 | func (m *RequestMeta) Size() (n int) { 248 | var l int 249 | _ = l 250 | l = len(m.ReqId) 251 | if l > 0 { 252 | n += 1 + l + sovTransport(uint64(l)) 253 | } 254 | l = len(m.ClientName) 255 | if l > 0 { 256 | n += 1 + l + sovTransport(uint64(l)) 257 | } 258 | return n 259 | } 260 | 261 | func (m *ErrorResponse) Size() (n int) { 262 | var l int 263 | _ = l 264 | l = len(m.Code) 265 | if l > 0 { 266 | n += 1 + l + sovTransport(uint64(l)) 267 | } 268 | l = len(m.Msg) 269 | if l > 0 { 270 | n += 1 + l + sovTransport(uint64(l)) 271 | } 272 | return n 273 | } 274 | 275 | func sovTransport(x uint64) (n int) { 276 | for { 277 | n++ 278 | x >>= 7 279 | if x == 0 { 280 | break 281 | } 282 | } 283 | return n 284 | } 285 | func sozTransport(x uint64) (n int) { 286 | return sovTransport(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 287 | } 288 | func (m *Request) Unmarshal(dAtA []byte) error { 289 | l := len(dAtA) 290 | iNdEx := 0 291 | for iNdEx < l { 292 | preIndex := iNdEx 293 | var wire uint64 294 | for shift := uint(0); ; shift += 7 { 295 | if shift >= 64 { 296 | return ErrIntOverflowTransport 297 | } 298 | if iNdEx >= l { 299 | return io.ErrUnexpectedEOF 300 | } 301 | b := dAtA[iNdEx] 302 | iNdEx++ 303 | wire |= (uint64(b) & 0x7F) << shift 304 | if b < 0x80 { 305 | break 306 | } 307 | } 308 | fieldNum := int32(wire >> 3) 309 | wireType := int(wire & 0x7) 310 | if wireType == 4 { 311 | return fmt.Errorf("proto: Request: wiretype end group for non-group") 312 | } 313 | if fieldNum <= 0 { 314 | return fmt.Errorf("proto: Request: illegal tag %d (wire type %d)", fieldNum, wire) 315 | } 316 | switch fieldNum { 317 | case 1: 318 | if wireType != 2 { 319 | return fmt.Errorf("proto: wrong wireType = %d for field Meta", wireType) 320 | } 321 | var msglen int 322 | for shift := uint(0); ; shift += 7 { 323 | if shift >= 64 { 324 | return ErrIntOverflowTransport 325 | } 326 | if iNdEx >= l { 327 | return io.ErrUnexpectedEOF 328 | } 329 | b := dAtA[iNdEx] 330 | iNdEx++ 331 | msglen |= (int(b) & 0x7F) << shift 332 | if b < 0x80 { 333 | break 334 | } 335 | } 336 | if msglen < 0 { 337 | return ErrInvalidLengthTransport 338 | } 339 | postIndex := iNdEx + msglen 340 | if postIndex > l { 341 | return io.ErrUnexpectedEOF 342 | } 343 | if m.Meta == nil { 344 | m.Meta = &RequestMeta{} 345 | } 346 | if err := m.Meta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { 347 | return err 348 | } 349 | iNdEx = postIndex 350 | case 2: 351 | if wireType != 2 { 352 | return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) 353 | } 354 | var byteLen int 355 | for shift := uint(0); ; shift += 7 { 356 | if shift >= 64 { 357 | return ErrIntOverflowTransport 358 | } 359 | if iNdEx >= l { 360 | return io.ErrUnexpectedEOF 361 | } 362 | b := dAtA[iNdEx] 363 | iNdEx++ 364 | byteLen |= (int(b) & 0x7F) << shift 365 | if b < 0x80 { 366 | break 367 | } 368 | } 369 | if byteLen < 0 { 370 | return ErrInvalidLengthTransport 371 | } 372 | postIndex := iNdEx + byteLen 373 | if postIndex > l { 374 | return io.ErrUnexpectedEOF 375 | } 376 | m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) 377 | if m.Payload == nil { 378 | m.Payload = []byte{} 379 | } 380 | iNdEx = postIndex 381 | default: 382 | iNdEx = preIndex 383 | skippy, err := skipTransport(dAtA[iNdEx:]) 384 | if err != nil { 385 | return err 386 | } 387 | if skippy < 0 { 388 | return ErrInvalidLengthTransport 389 | } 390 | if (iNdEx + skippy) > l { 391 | return io.ErrUnexpectedEOF 392 | } 393 | iNdEx += skippy 394 | } 395 | } 396 | 397 | if iNdEx > l { 398 | return io.ErrUnexpectedEOF 399 | } 400 | return nil 401 | } 402 | func (m *RequestMeta) Unmarshal(dAtA []byte) error { 403 | l := len(dAtA) 404 | iNdEx := 0 405 | for iNdEx < l { 406 | preIndex := iNdEx 407 | var wire uint64 408 | for shift := uint(0); ; shift += 7 { 409 | if shift >= 64 { 410 | return ErrIntOverflowTransport 411 | } 412 | if iNdEx >= l { 413 | return io.ErrUnexpectedEOF 414 | } 415 | b := dAtA[iNdEx] 416 | iNdEx++ 417 | wire |= (uint64(b) & 0x7F) << shift 418 | if b < 0x80 { 419 | break 420 | } 421 | } 422 | fieldNum := int32(wire >> 3) 423 | wireType := int(wire & 0x7) 424 | if wireType == 4 { 425 | return fmt.Errorf("proto: RequestMeta: wiretype end group for non-group") 426 | } 427 | if fieldNum <= 0 { 428 | return fmt.Errorf("proto: RequestMeta: illegal tag %d (wire type %d)", fieldNum, wire) 429 | } 430 | switch fieldNum { 431 | case 1: 432 | if wireType != 2 { 433 | return fmt.Errorf("proto: wrong wireType = %d for field ReqId", wireType) 434 | } 435 | var stringLen uint64 436 | for shift := uint(0); ; shift += 7 { 437 | if shift >= 64 { 438 | return ErrIntOverflowTransport 439 | } 440 | if iNdEx >= l { 441 | return io.ErrUnexpectedEOF 442 | } 443 | b := dAtA[iNdEx] 444 | iNdEx++ 445 | stringLen |= (uint64(b) & 0x7F) << shift 446 | if b < 0x80 { 447 | break 448 | } 449 | } 450 | intStringLen := int(stringLen) 451 | if intStringLen < 0 { 452 | return ErrInvalidLengthTransport 453 | } 454 | postIndex := iNdEx + intStringLen 455 | if postIndex > l { 456 | return io.ErrUnexpectedEOF 457 | } 458 | m.ReqId = string(dAtA[iNdEx:postIndex]) 459 | iNdEx = postIndex 460 | case 3: 461 | if wireType != 2 { 462 | return fmt.Errorf("proto: wrong wireType = %d for field ClientName", wireType) 463 | } 464 | var stringLen uint64 465 | for shift := uint(0); ; shift += 7 { 466 | if shift >= 64 { 467 | return ErrIntOverflowTransport 468 | } 469 | if iNdEx >= l { 470 | return io.ErrUnexpectedEOF 471 | } 472 | b := dAtA[iNdEx] 473 | iNdEx++ 474 | stringLen |= (uint64(b) & 0x7F) << shift 475 | if b < 0x80 { 476 | break 477 | } 478 | } 479 | intStringLen := int(stringLen) 480 | if intStringLen < 0 { 481 | return ErrInvalidLengthTransport 482 | } 483 | postIndex := iNdEx + intStringLen 484 | if postIndex > l { 485 | return io.ErrUnexpectedEOF 486 | } 487 | m.ClientName = string(dAtA[iNdEx:postIndex]) 488 | iNdEx = postIndex 489 | default: 490 | iNdEx = preIndex 491 | skippy, err := skipTransport(dAtA[iNdEx:]) 492 | if err != nil { 493 | return err 494 | } 495 | if skippy < 0 { 496 | return ErrInvalidLengthTransport 497 | } 498 | if (iNdEx + skippy) > l { 499 | return io.ErrUnexpectedEOF 500 | } 501 | iNdEx += skippy 502 | } 503 | } 504 | 505 | if iNdEx > l { 506 | return io.ErrUnexpectedEOF 507 | } 508 | return nil 509 | } 510 | func (m *ErrorResponse) Unmarshal(dAtA []byte) error { 511 | l := len(dAtA) 512 | iNdEx := 0 513 | for iNdEx < l { 514 | preIndex := iNdEx 515 | var wire uint64 516 | for shift := uint(0); ; shift += 7 { 517 | if shift >= 64 { 518 | return ErrIntOverflowTransport 519 | } 520 | if iNdEx >= l { 521 | return io.ErrUnexpectedEOF 522 | } 523 | b := dAtA[iNdEx] 524 | iNdEx++ 525 | wire |= (uint64(b) & 0x7F) << shift 526 | if b < 0x80 { 527 | break 528 | } 529 | } 530 | fieldNum := int32(wire >> 3) 531 | wireType := int(wire & 0x7) 532 | if wireType == 4 { 533 | return fmt.Errorf("proto: ErrorResponse: wiretype end group for non-group") 534 | } 535 | if fieldNum <= 0 { 536 | return fmt.Errorf("proto: ErrorResponse: illegal tag %d (wire type %d)", fieldNum, wire) 537 | } 538 | switch fieldNum { 539 | case 1: 540 | if wireType != 2 { 541 | return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) 542 | } 543 | var stringLen uint64 544 | for shift := uint(0); ; shift += 7 { 545 | if shift >= 64 { 546 | return ErrIntOverflowTransport 547 | } 548 | if iNdEx >= l { 549 | return io.ErrUnexpectedEOF 550 | } 551 | b := dAtA[iNdEx] 552 | iNdEx++ 553 | stringLen |= (uint64(b) & 0x7F) << shift 554 | if b < 0x80 { 555 | break 556 | } 557 | } 558 | intStringLen := int(stringLen) 559 | if intStringLen < 0 { 560 | return ErrInvalidLengthTransport 561 | } 562 | postIndex := iNdEx + intStringLen 563 | if postIndex > l { 564 | return io.ErrUnexpectedEOF 565 | } 566 | m.Code = string(dAtA[iNdEx:postIndex]) 567 | iNdEx = postIndex 568 | case 2: 569 | if wireType != 2 { 570 | return fmt.Errorf("proto: wrong wireType = %d for field Msg", wireType) 571 | } 572 | var stringLen uint64 573 | for shift := uint(0); ; shift += 7 { 574 | if shift >= 64 { 575 | return ErrIntOverflowTransport 576 | } 577 | if iNdEx >= l { 578 | return io.ErrUnexpectedEOF 579 | } 580 | b := dAtA[iNdEx] 581 | iNdEx++ 582 | stringLen |= (uint64(b) & 0x7F) << shift 583 | if b < 0x80 { 584 | break 585 | } 586 | } 587 | intStringLen := int(stringLen) 588 | if intStringLen < 0 { 589 | return ErrInvalidLengthTransport 590 | } 591 | postIndex := iNdEx + intStringLen 592 | if postIndex > l { 593 | return io.ErrUnexpectedEOF 594 | } 595 | m.Msg = string(dAtA[iNdEx:postIndex]) 596 | iNdEx = postIndex 597 | default: 598 | iNdEx = preIndex 599 | skippy, err := skipTransport(dAtA[iNdEx:]) 600 | if err != nil { 601 | return err 602 | } 603 | if skippy < 0 { 604 | return ErrInvalidLengthTransport 605 | } 606 | if (iNdEx + skippy) > l { 607 | return io.ErrUnexpectedEOF 608 | } 609 | iNdEx += skippy 610 | } 611 | } 612 | 613 | if iNdEx > l { 614 | return io.ErrUnexpectedEOF 615 | } 616 | return nil 617 | } 618 | func skipTransport(dAtA []byte) (n int, err error) { 619 | l := len(dAtA) 620 | iNdEx := 0 621 | for iNdEx < l { 622 | var wire uint64 623 | for shift := uint(0); ; shift += 7 { 624 | if shift >= 64 { 625 | return 0, ErrIntOverflowTransport 626 | } 627 | if iNdEx >= l { 628 | return 0, io.ErrUnexpectedEOF 629 | } 630 | b := dAtA[iNdEx] 631 | iNdEx++ 632 | wire |= (uint64(b) & 0x7F) << shift 633 | if b < 0x80 { 634 | break 635 | } 636 | } 637 | wireType := int(wire & 0x7) 638 | switch wireType { 639 | case 0: 640 | for shift := uint(0); ; shift += 7 { 641 | if shift >= 64 { 642 | return 0, ErrIntOverflowTransport 643 | } 644 | if iNdEx >= l { 645 | return 0, io.ErrUnexpectedEOF 646 | } 647 | iNdEx++ 648 | if dAtA[iNdEx-1] < 0x80 { 649 | break 650 | } 651 | } 652 | return iNdEx, nil 653 | case 1: 654 | iNdEx += 8 655 | return iNdEx, nil 656 | case 2: 657 | var length int 658 | for shift := uint(0); ; shift += 7 { 659 | if shift >= 64 { 660 | return 0, ErrIntOverflowTransport 661 | } 662 | if iNdEx >= l { 663 | return 0, io.ErrUnexpectedEOF 664 | } 665 | b := dAtA[iNdEx] 666 | iNdEx++ 667 | length |= (int(b) & 0x7F) << shift 668 | if b < 0x80 { 669 | break 670 | } 671 | } 672 | iNdEx += length 673 | if length < 0 { 674 | return 0, ErrInvalidLengthTransport 675 | } 676 | return iNdEx, nil 677 | case 3: 678 | for { 679 | var innerWire uint64 680 | var start int = iNdEx 681 | for shift := uint(0); ; shift += 7 { 682 | if shift >= 64 { 683 | return 0, ErrIntOverflowTransport 684 | } 685 | if iNdEx >= l { 686 | return 0, io.ErrUnexpectedEOF 687 | } 688 | b := dAtA[iNdEx] 689 | iNdEx++ 690 | innerWire |= (uint64(b) & 0x7F) << shift 691 | if b < 0x80 { 692 | break 693 | } 694 | } 695 | innerWireType := int(innerWire & 0x7) 696 | if innerWireType == 4 { 697 | break 698 | } 699 | next, err := skipTransport(dAtA[start:]) 700 | if err != nil { 701 | return 0, err 702 | } 703 | iNdEx = start + next 704 | } 705 | return iNdEx, nil 706 | case 4: 707 | return iNdEx, nil 708 | case 5: 709 | iNdEx += 4 710 | return iNdEx, nil 711 | default: 712 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 713 | } 714 | } 715 | panic("unreachable") 716 | } 717 | 718 | var ( 719 | ErrInvalidLengthTransport = fmt.Errorf("proto: negative length found during unmarshaling") 720 | ErrIntOverflowTransport = fmt.Errorf("proto: integer overflow") 721 | ) 722 | 723 | func init() { proto.RegisterFile("Transport.proto", fileDescriptorTransport) } 724 | 725 | var fileDescriptorTransport = []byte{ 726 | // 228 bytes of a gzipped FileDescriptorProto 727 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x0f, 0x29, 0x4a, 0xcc, 728 | 0x2b, 0x2e, 0xc8, 0x2f, 0x2a, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4a, 0xcf, 0x4f, 729 | 0xcf, 0x8f, 0x07, 0xb3, 0x95, 0x02, 0xb8, 0xd8, 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 730 | 0xb4, 0xb9, 0x58, 0x72, 0x53, 0x4b, 0x12, 0x25, 0x18, 0x15, 0x18, 0x35, 0xb8, 0x8d, 0xc4, 0xf5, 731 | 0x10, 0xaa, 0xf4, 0xa0, 0x4a, 0x7c, 0x53, 0x4b, 0x12, 0x83, 0xc0, 0x8a, 0x84, 0x24, 0xb8, 0xd8, 732 | 0x0b, 0x12, 0x2b, 0x73, 0xf2, 0x13, 0x53, 0x24, 0x98, 0x14, 0x18, 0x35, 0x78, 0x82, 0x60, 0x5c, 733 | 0x25, 0x57, 0x2e, 0x6e, 0x24, 0xe5, 0x42, 0xa2, 0x5c, 0x6c, 0x45, 0xa9, 0x85, 0xf1, 0x99, 0x29, 734 | 0x60, 0x73, 0x39, 0x83, 0x58, 0x8b, 0x52, 0x0b, 0x3d, 0x53, 0x84, 0xe4, 0xb9, 0xb8, 0x93, 0x73, 735 | 0x32, 0x53, 0xf3, 0x4a, 0xe2, 0xf3, 0x12, 0x73, 0x53, 0x25, 0x98, 0xc1, 0x72, 0x5c, 0x10, 0x21, 736 | 0xbf, 0xc4, 0xdc, 0x54, 0x25, 0x53, 0x2e, 0x5e, 0xd7, 0xa2, 0xa2, 0xfc, 0xa2, 0xa0, 0xd4, 0xe2, 737 | 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x21, 0x2e, 0x96, 0xe4, 0xfc, 0x94, 0x54, 0xa8, 0x31, 0x60, 738 | 0xb6, 0x90, 0x00, 0x17, 0x73, 0x6e, 0x71, 0x3a, 0xd8, 0x05, 0x9c, 0x41, 0x20, 0xa6, 0x93, 0xc0, 739 | 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 740 | 0x43, 0x12, 0x1b, 0xd8, 0x0b, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4a, 0x3b, 0xa1, 0x17, 741 | 0x07, 0x01, 0x00, 0x00, 742 | } 743 | -------------------------------------------------------------------------------- /addons/transport/http/serialization/gogo_proto/Transport.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package gogo_proto; 4 | 5 | message Request { 6 | RequestMeta meta = 1; 7 | bytes payload = 2; 8 | } 9 | 10 | message RequestMeta { 11 | string req_id = 1; 12 | string client_name = 3; 13 | } 14 | 15 | message ErrorResponse { 16 | string code = 1; 17 | string msg = 2; 18 | } 19 | -------------------------------------------------------------------------------- /addons/transport/http/serialization/gogo_proto/generate_proto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | cd "$( dirname "$0" )" 4 | 5 | PKG="github.com/pasztorpisti/nano/addons/transport/http/serialization/gogo_proto" 6 | protoc --gogofaster_out="M${PKG}/Transport.proto=${PKG}:." Transport.proto 7 | -------------------------------------------------------------------------------- /addons/transport/http/serialization/gogo_proto/gogo_proto_serializer.go: -------------------------------------------------------------------------------- 1 | package gogo_proto 2 | 3 | import ( 4 | "io/ioutil" 5 | "mime" 6 | "net/http" 7 | "reflect" 8 | 9 | "github.com/gogo/protobuf/proto" 10 | "github.com/pasztorpisti/nano" 11 | "github.com/pasztorpisti/nano/addons/transport/http/config" 12 | "github.com/pasztorpisti/nano/addons/transport/http/serialization" 13 | "github.com/pasztorpisti/nano/addons/util" 14 | ) 15 | 16 | var ClientSideSerializer = &serialization.ClientSideSerializer{ 17 | ReqSerializer: &reqSerializer{}, 18 | RespDeserializer: &respDeserializer{}, 19 | } 20 | 21 | var ServerSideSerializer = &serialization.ServerSideSerializer{ 22 | ReqDeserializer: &reqDeserializer{}, 23 | RespSerializer: &respSerializer{}, 24 | } 25 | 26 | type reqSerializer struct{} 27 | 28 | func (reqSerializer) SerializeRequest(ec *config.EndpointConfig, 29 | c *nano.Ctx, req interface{}) (h http.Header, body []byte, err error) { 30 | m, ok := req.(proto.Message) 31 | if !ok { 32 | err = util.Errf(nil, "expected proto.Message, got %T", req) 33 | return 34 | } 35 | payload, err := proto.Marshal(m) 36 | if err != nil { 37 | err = util.Err(err, "error marshaling request") 38 | return 39 | } 40 | 41 | request := &Request{ 42 | Meta: &RequestMeta{ 43 | ReqId: c.ReqID, 44 | ClientName: c.ClientName, 45 | }, 46 | Payload: payload, 47 | } 48 | body, err = proto.Marshal(request) 49 | if err != nil { 50 | err = util.Err(err, "error marshaling request") 51 | return 52 | } 53 | 54 | h = make(http.Header, 1) 55 | h.Set("Content-Type", "application/x-protobuf") 56 | return 57 | } 58 | 59 | type reqDeserializer struct{} 60 | 61 | func (reqDeserializer) DeserializeRequest(ec *config.EndpointConfig, 62 | r *http.Request) (req interface{}, ri serialization.ReqInfo, err error) { 63 | ct := r.Header.Get("Content-Type") 64 | if ct == "" { 65 | err = util.ErrCode(nil, config.ErrorCodeBadRequestContentType, 66 | "missing request Content-Type header") 67 | return 68 | } 69 | mt, _, err := mime.ParseMediaType(ct) 70 | if err != nil { 71 | err = util.ErrCodef(err, config.ErrorCodeBadRequestContentType, 72 | "error parsing request Content-Type: %q", ct) 73 | return 74 | } 75 | if mt != "application/x-protobuf" { 76 | err = util.ErrCodef(nil, config.ErrorCodeBadRequestContentType, 77 | "unsupported request Content-Type: %q", ct) 78 | return 79 | } 80 | 81 | body, err := ioutil.ReadAll(r.Body) 82 | if err != nil { 83 | err = util.Errf(err, "error reading request body") 84 | return 85 | } 86 | 87 | request := new(Request) 88 | err = proto.Unmarshal(body, request) 89 | if err != nil { 90 | err = util.ErrCode(err, config.ErrorCodeBadRequest, 91 | "error unmarshaling proto request") 92 | return 93 | } 94 | 95 | req = reflect.New(ec.ReqType).Interface() 96 | m, ok := req.(proto.Message) 97 | if !ok { 98 | err = util.Errf(nil, "request doesn't implement proto.Message: %T", req) 99 | return 100 | } 101 | err = proto.Unmarshal(request.Payload, m) 102 | if err != nil { 103 | err = util.ErrCodef(err, config.ErrorCodeBadRequest, 104 | "error unmarshaling request of type %T", req) 105 | return 106 | } 107 | 108 | ri.ReqID = request.Meta.ReqId 109 | ri.ClientName = request.Meta.ClientName 110 | return 111 | } 112 | 113 | type respSerializer struct{} 114 | 115 | func (respSerializer) SerializeResponse(ec *config.EndpointConfig, c *nano.Ctx, 116 | w http.ResponseWriter, r *http.Request, resp interface{}, errResp error) error { 117 | if errResp != nil { 118 | return sendErrorResponse(w, errResp) 119 | } 120 | 121 | var body []byte 122 | if ec.RespType != nil { 123 | m, ok := resp.(proto.Message) 124 | if !ok { 125 | sendErrorResponse(w, serverError) 126 | return util.Errf(nil, "expected proto.Message, got %T", resp) 127 | } 128 | var err error 129 | body, err = proto.Marshal(m) 130 | if err != nil { 131 | sendErrorResponse(w, serverError) 132 | return util.Err(err, "error marshaling response") 133 | } 134 | } 135 | 136 | w.Header().Set("Content-Type", "application/x-protobuf") 137 | w.WriteHeader(200) 138 | _, err := w.Write(body) 139 | if err != nil { 140 | return util.Err(err, "error writing marshaled response") 141 | } 142 | return nil 143 | } 144 | 145 | var serverError = util.ErrCode(nil, config.ErrorCodeServerError, 146 | "Internal Server Error") 147 | 148 | func sendErrorResponse(w http.ResponseWriter, errResp error) error { 149 | code := util.GetErrCode(errResp) 150 | body, err := proto.Marshal(&ErrorResponse{ 151 | Code: code, 152 | Msg: errResp.Error(), 153 | }) 154 | if err != nil { 155 | return util.Err(err, "error marshaling error response") 156 | } 157 | w.Header().Set("Content-Type", "application/x-protobuf") 158 | w.WriteHeader(config.ErrorCodeToHTTPStatus(code)) 159 | _, err = w.Write(body) 160 | if err != nil { 161 | return util.Err(err, "error writing error response") 162 | } 163 | return nil 164 | } 165 | 166 | type respDeserializer struct{} 167 | 168 | func (respDeserializer) DeserializeResponse(ec *config.EndpointConfig, c *nano.Ctx, 169 | resp *http.Response) (respObj interface{}, respErr error, err error) { 170 | ct := resp.Header.Get("Content-Type") 171 | if ct == "" { 172 | err = util.Err(nil, "missing response Content-Type header") 173 | return 174 | } 175 | mt, _, err := mime.ParseMediaType(ct) 176 | if err != nil { 177 | err = util.Errf(err, "error parsing response Content-Type: %q", ct) 178 | return 179 | } 180 | if mt != "application/x-protobuf" { 181 | err = util.Errf(nil, "unsupported response Content-Type: %q", ct) 182 | return 183 | } 184 | 185 | body, err := ioutil.ReadAll(resp.Body) 186 | if err != nil { 187 | err = util.Errf(err, "error reading response body") 188 | return 189 | } 190 | 191 | if resp.StatusCode/100 != 2 { 192 | m := new(ErrorResponse) 193 | err = proto.Unmarshal(body, m) 194 | if err != nil { 195 | err = util.Err(err, "error unmarshaling error response") 196 | return 197 | } 198 | respErr = util.ErrCode(nil, m.Code, m.Msg) 199 | return 200 | } 201 | 202 | if ec.RespType == nil { 203 | if len(body) != 0 { 204 | err = util.Err(nil, "unexpected response body") 205 | } 206 | return 207 | } 208 | 209 | respObj = reflect.New(ec.RespType).Interface() 210 | m, ok := respObj.(proto.Message) 211 | if !ok { 212 | err = util.Errf(nil, "response type isn't a proto.Message: %T", respObj) 213 | return 214 | } 215 | err = proto.Unmarshal(body, m) 216 | if err != nil { 217 | err = util.Err(err, "error unmarshaling response") 218 | return 219 | } 220 | return 221 | } 222 | -------------------------------------------------------------------------------- /addons/transport/http/serialization/json/json_serializer.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "io/ioutil" 7 | "mime" 8 | "net/http" 9 | "reflect" 10 | "strings" 11 | 12 | "github.com/pasztorpisti/nano" 13 | "github.com/pasztorpisti/nano/addons/transport/http/config" 14 | "github.com/pasztorpisti/nano/addons/transport/http/serialization" 15 | "github.com/pasztorpisti/nano/addons/util" 16 | ) 17 | 18 | var ClientSideSerializer = &serialization.ClientSideSerializer{ 19 | ReqSerializer: &reqSerializer{}, 20 | RespDeserializer: &respDeserializer{}, 21 | } 22 | 23 | var ServerSideSerializer = &serialization.ServerSideSerializer{ 24 | ReqDeserializer: &reqDeserializer{}, 25 | RespSerializer: &respSerializer{}, 26 | } 27 | 28 | type ErrorResponse struct { 29 | Code string `json:"code,omitempty"` 30 | Msg string `json:"msg,omitempty"` 31 | } 32 | 33 | const ( 34 | HeaderReqID = "X-Nano-Req-Id" 35 | HeaderClientName = "X-Nano-Client-Name" 36 | ) 37 | 38 | type reqSerializer struct{} 39 | 40 | func (reqSerializer) SerializeRequest(ec *config.EndpointConfig, 41 | c *nano.Ctx, req interface{}) (h http.Header, body []byte, err error) { 42 | if ec.HasReqContent { 43 | body, err = json.Marshal(req) 44 | if err != nil { 45 | err = util.Err(err, "error marshaling request") 46 | return 47 | } 48 | } 49 | 50 | h = make(http.Header, 3) 51 | if ec.HasReqContent { 52 | h.Set("Content-Type", "application/json; charset=utf-8") 53 | } 54 | if c.ReqID != "" { 55 | h.Set(HeaderReqID, c.ReqID) 56 | } 57 | if c.ClientName != "" { 58 | h.Set(HeaderClientName, c.ClientName) 59 | } 60 | return 61 | } 62 | 63 | type reqDeserializer struct{} 64 | 65 | func (reqDeserializer) DeserializeRequest(ec *config.EndpointConfig, 66 | r *http.Request) (req interface{}, ri serialization.ReqInfo, err error) { 67 | if ec.HasReqContent { 68 | ct := r.Header.Get("Content-Type") 69 | if ct == "" { 70 | err = util.ErrCode(nil, config.ErrorCodeBadRequestContentType, 71 | "missing request Content-Type header") 72 | return 73 | } 74 | mt, mp, err2 := mime.ParseMediaType(ct) 75 | if err2 != nil { 76 | err = util.ErrCodef(err2, config.ErrorCodeBadRequestContentType, 77 | "error parsing request Content-Type: %q", ct) 78 | return 79 | } 80 | if mt != "application/json" { 81 | err = util.ErrCodef(nil, config.ErrorCodeBadRequestContentType, 82 | "unsupported request Content-Type: %q", ct) 83 | return 84 | } 85 | if cs, ok := mp["charset"]; ok && strings.ToLower(cs) != "utf-8" { 86 | err = util.ErrCodef(nil, config.ErrorCodeBadRequestContentType, 87 | "unsupported request json charset: %v", cs) 88 | return 89 | } 90 | } 91 | 92 | req = reflect.New(ec.ReqType).Interface() 93 | 94 | if ec.HasReqContent { 95 | var body []byte 96 | body, err = ioutil.ReadAll(r.Body) 97 | if err != nil { 98 | err = util.Err(err, "error reading request body") 99 | return 100 | } 101 | err = json.Unmarshal(body, req) 102 | if err != nil { 103 | err = util.ErrCodef(err, config.ErrorCodeBadRequest, 104 | "error unmarshaling request of type %T", req) 105 | return 106 | } 107 | } else if r.ContentLength > 0 { 108 | err = util.Err(nil, "unexpected request content") 109 | return 110 | } else if r.ContentLength < 0 { 111 | buf := make([]byte, 1) 112 | n, err2 := r.Body.Read(buf) 113 | if err2 != nil && err2 != io.EOF { 114 | err = util.Err(err2, "error reading request body") 115 | return 116 | } 117 | if n != 0 { 118 | err = util.Err(nil, "unexpected request content") 119 | return 120 | } 121 | } 122 | 123 | ri.ReqID = r.Header.Get(HeaderReqID) 124 | ri.ClientName = r.Header.Get(HeaderClientName) 125 | return 126 | } 127 | 128 | type respSerializer struct{} 129 | 130 | func (respSerializer) SerializeResponse(ec *config.EndpointConfig, c *nano.Ctx, 131 | w http.ResponseWriter, r *http.Request, resp interface{}, errResp error) error { 132 | if errResp != nil { 133 | return sendErrorResponse(w, errResp) 134 | } 135 | 136 | if ec.RespType == nil { 137 | return nil 138 | } 139 | 140 | var body []byte 141 | body, err := json.Marshal(resp) 142 | if err != nil { 143 | sendErrorResponse(w, serverError) 144 | return util.Err(err, "error marshaling response") 145 | } 146 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 147 | w.WriteHeader(200) 148 | _, err = w.Write(body) 149 | if err != nil { 150 | return util.Err(err, "error writing marshaled response") 151 | } 152 | return nil 153 | } 154 | 155 | var serverError = util.ErrCode(nil, config.ErrorCodeServerError, 156 | "Internal Server Error") 157 | 158 | func sendErrorResponse(w http.ResponseWriter, errResp error) error { 159 | code := util.GetErrCode(errResp) 160 | body, err := json.Marshal(&ErrorResponse{ 161 | Code: code, 162 | Msg: errResp.Error(), 163 | }) 164 | if err != nil { 165 | return util.Err(err, "error marshaling error response") 166 | } 167 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 168 | w.WriteHeader(config.ErrorCodeToHTTPStatus(code)) 169 | _, err = w.Write(body) 170 | if err != nil { 171 | return util.Err(err, "error writing error response") 172 | } 173 | return nil 174 | } 175 | 176 | type respDeserializer struct{} 177 | 178 | func (respDeserializer) DeserializeResponse(ec *config.EndpointConfig, c *nano.Ctx, 179 | resp *http.Response) (respObj interface{}, respErr error, err error) { 180 | if ec.RespType != nil { 181 | ct := resp.Header.Get("Content-Type") 182 | if ct == "" { 183 | if resp.StatusCode/100 != 2 { 184 | err = util.Errf(nil, "HTTP status: %v", resp.Status) 185 | return 186 | } 187 | err = util.Err(nil, "missing response Content-Type header") 188 | return 189 | } 190 | mt, mp, err2 := mime.ParseMediaType(ct) 191 | if err2 != nil { 192 | err = util.Errf(err2, "error parsing response Content-Type: %q", ct) 193 | return 194 | } 195 | if mt != "application/json" { 196 | if resp.StatusCode/100 != 2 { 197 | err = util.Errf(nil, "HTTP status: %v", resp.Status) 198 | return 199 | } 200 | err = util.Errf(nil, "unsupported response Content-Type: %q", ct) 201 | return 202 | } 203 | if cs, ok := mp["charset"]; ok && strings.ToLower(cs) != "utf-8" { 204 | err = util.Errf(nil, "unsupported response json charset: %v", cs) 205 | return 206 | } 207 | } 208 | 209 | if resp.StatusCode/100 != 2 { 210 | body, err2 := ioutil.ReadAll(resp.Body) 211 | if err2 != nil { 212 | err = util.Errf(err2, "error reading response body") 213 | return 214 | } 215 | m := new(ErrorResponse) 216 | err = json.Unmarshal(body, m) 217 | if err != nil { 218 | err = util.Err(err, "error unmarshaling error response") 219 | return 220 | } 221 | respErr = util.ErrCode(nil, m.Code, m.Msg) 222 | return 223 | } 224 | 225 | if ec.RespType != nil { 226 | body, err2 := ioutil.ReadAll(resp.Body) 227 | if err2 != nil { 228 | err = util.Errf(err2, "error reading response body") 229 | return 230 | } 231 | respObj = reflect.New(ec.RespType).Interface() 232 | err = json.Unmarshal(body, respObj) 233 | if err != nil { 234 | err = util.Err(err, "error unmarshaling response") 235 | return 236 | } 237 | return 238 | } else if resp.ContentLength > 0 { 239 | err = util.Err(nil, "unexpected response content") 240 | return 241 | } else if resp.ContentLength < 0 { 242 | buf := make([]byte, 1) 243 | n, err2 := resp.Body.Read(buf) 244 | if err2 != nil && err2 != io.EOF { 245 | err = util.Err(err2, "error reading response body") 246 | return 247 | } 248 | if n != 0 { 249 | err = util.Err(nil, "unexpected response content") 250 | return 251 | } 252 | return 253 | } 254 | 255 | return 256 | } 257 | -------------------------------------------------------------------------------- /addons/transport/http/serialization/json/json_serializer_test.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "net/http/httptest" 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/pasztorpisti/nano" 12 | "github.com/pasztorpisti/nano/addons/transport/http/config" 13 | "github.com/pasztorpisti/nano/addons/util" 14 | ) 15 | 16 | type ReqNoContent struct{} 17 | 18 | var endpointConfigNoContent = &config.EndpointConfig{ 19 | Method: "GET", 20 | Path: "/path", 21 | HasReqContent: false, 22 | ReqType: reflect.TypeOf((*ReqNoContent)(nil)).Elem(), 23 | RespType: nil, 24 | } 25 | 26 | type ReqWithContent struct { 27 | S string 28 | I int 29 | } 30 | 31 | type RespWithContent struct { 32 | B0 bool 33 | B1 bool 34 | } 35 | 36 | var endpointConfigWithContent = &config.EndpointConfig{ 37 | Method: "POST", 38 | Path: "/path", 39 | HasReqContent: true, 40 | ReqType: reflect.TypeOf((*ReqWithContent)(nil)).Elem(), 41 | RespType: reflect.TypeOf((*RespWithContent)(nil)).Elem(), 42 | } 43 | 44 | const ( 45 | testReqID = "TestReqID" 46 | testClientName = "test" 47 | jsonContentType = "application/json; charset=utf-8" 48 | ) 49 | 50 | func newCtx() *nano.Ctx { 51 | return &nano.Ctx{ 52 | ReqID: testReqID, 53 | Context: context.Background(), 54 | ClientName: testClientName, 55 | } 56 | } 57 | 58 | func TestReqSerialization_NoContent(t *testing.T) { 59 | c := newCtx() 60 | ec := endpointConfigNoContent 61 | h, body, err := ClientSideSerializer.ReqSerializer.SerializeRequest( 62 | ec, c, &ReqNoContent{}) 63 | if err != nil { 64 | t.Errorf("SerializeRequest failed :: %v", err) 65 | t.FailNow() 66 | } 67 | 68 | if v := h.Get(HeaderReqID); v != testReqID { 69 | t.Errorf("header(%v) == %q, want %q", HeaderReqID, v, testReqID) 70 | } 71 | if v := h.Get(HeaderClientName); v != testClientName { 72 | t.Errorf("header(%v) == %q, want %q", HeaderClientName, v, testClientName) 73 | } 74 | if v := h.Get("Content-Type"); v != "" { 75 | t.Errorf("unexpected Content-Type header: %q", v) 76 | } 77 | if len(body) > 0 { 78 | t.Errorf("unexpected content: %v", body) 79 | } 80 | 81 | if t.Failed() { 82 | t.FailNow() 83 | } 84 | 85 | r := httptest.NewRequest(ec.Method, ec.Path, nil) 86 | r.Header = h 87 | req, ri, err := ServerSideSerializer.ReqDeserializer.DeserializeRequest(ec, r) 88 | if err != nil { 89 | t.Errorf("DeserializeRequest failed :: %v", err) 90 | t.FailNow() 91 | } 92 | if reflect.TypeOf(req) != reflect.PtrTo(ec.ReqType) { 93 | t.Errorf("deserialised req type == %v, want %v", reflect.TypeOf(req), 94 | reflect.PtrTo(ec.ReqType)) 95 | } 96 | if ri.ReqID != testReqID { 97 | t.Errorf("deserialised req id == %q, want %q", ri.ReqID, testReqID) 98 | } 99 | if ri.ClientName != testClientName { 100 | t.Errorf("deserialised req client name == %q, want %q", ri.ClientName, testClientName) 101 | } 102 | } 103 | 104 | func TestReqSerialization_WithContent(t *testing.T) { 105 | c := newCtx() 106 | ec := endpointConfigWithContent 107 | inputReq := &ReqWithContent{ 108 | S: "str", 109 | I: 42, 110 | } 111 | h, body, err := ClientSideSerializer.ReqSerializer.SerializeRequest( 112 | ec, c, inputReq) 113 | if err != nil { 114 | t.Errorf("SerializeRequest failed :: %v", err) 115 | t.FailNow() 116 | } 117 | 118 | if v := h.Get(HeaderReqID); v != testReqID { 119 | t.Errorf("header(%v) == %q, want %q", HeaderReqID, v, testReqID) 120 | } 121 | if v := h.Get(HeaderClientName); v != testClientName { 122 | t.Errorf("header(%v) == %q, want %q", HeaderClientName, v, testClientName) 123 | } 124 | if v := h.Get("Content-Type"); v != jsonContentType { 125 | t.Errorf("Content-Type header == %q, want %q", v, jsonContentType) 126 | } 127 | if len(body) == 0 { 128 | t.Error("no content") 129 | } 130 | 131 | if t.Failed() { 132 | t.FailNow() 133 | } 134 | 135 | r := httptest.NewRequest(ec.Method, ec.Path, bytes.NewReader(body)) 136 | r.Header = h 137 | reqObj, ri, err := ServerSideSerializer.ReqDeserializer.DeserializeRequest(ec, r) 138 | if err != nil { 139 | t.Errorf("DeserializeRequest failed :: %v", err) 140 | t.FailNow() 141 | } 142 | if reflect.TypeOf(reqObj) != reflect.PtrTo(ec.ReqType) { 143 | t.Errorf("deserialised req type == %v, want %v", reflect.TypeOf(reqObj), 144 | reflect.PtrTo(ec.ReqType)) 145 | } 146 | req := reqObj.(*ReqWithContent) 147 | if inputReq.S != req.S || inputReq.I != req.I { 148 | t.Errorf("desrialised req == %#v, want %#v", req, inputReq) 149 | } 150 | if ri.ReqID != testReqID { 151 | t.Errorf("deserialised req id == %q, want %q", ri.ReqID, testReqID) 152 | } 153 | if ri.ClientName != testClientName { 154 | t.Errorf("deserialised req client name == %q, want %q", ri.ClientName, testClientName) 155 | } 156 | } 157 | 158 | func TestRespSerialization_NoContent(t *testing.T) { 159 | c := newCtx() 160 | ec := endpointConfigNoContent 161 | w := httptest.NewRecorder() 162 | r := httptest.NewRequest(ec.Method, ec.Path, nil) 163 | err := ServerSideSerializer.SerializeResponse(ec, c, w, r, nil, nil) 164 | if err != nil { 165 | t.Errorf("SerializeResponse failed :: %v", err) 166 | t.FailNow() 167 | } 168 | 169 | if w.Code != 200 { 170 | t.Errorf("response status code %v, want %v", w.Code, 200) 171 | } 172 | if v := w.Header().Get("Content-Type"); v != "" { 173 | t.Errorf("unexpected Content-Type header: %q", v) 174 | } 175 | if w.Body.Len() > 0 { 176 | t.Errorf("unexpected content: %v", w.Body.Bytes()) 177 | } 178 | 179 | if t.Failed() { 180 | t.FailNow() 181 | } 182 | 183 | respObj, respErr, err := ClientSideSerializer.RespDeserializer.DeserializeResponse(ec, c, w.Result()) 184 | if err != nil { 185 | t.Errorf("DeserializeResponse failed :: %v", err) 186 | t.FailNow() 187 | } 188 | if respErr != nil { 189 | t.Errorf("unexpected respErr :: %v", respErr) 190 | } 191 | if respObj != nil { 192 | t.Errorf("unexpected respObj == %#v, want nil", respObj) 193 | } 194 | } 195 | 196 | func TestRespSerialization_WithContent(t *testing.T) { 197 | c := newCtx() 198 | ec := endpointConfigWithContent 199 | w := httptest.NewRecorder() 200 | r := httptest.NewRequest(ec.Method, ec.Path, nil) 201 | inputResp := &RespWithContent{ 202 | B0: false, 203 | B1: true, 204 | } 205 | err := ServerSideSerializer.SerializeResponse(ec, c, w, r, inputResp, nil) 206 | if err != nil { 207 | t.Errorf("SerializeResponse failed :: %v", err) 208 | t.FailNow() 209 | } 210 | 211 | if w.Code != 200 { 212 | t.Errorf("response status code %v, want %v", w.Code, 200) 213 | } 214 | if v := w.Header().Get("Content-Type"); v != jsonContentType { 215 | t.Errorf("Content-Type header == %q, want %q", v, jsonContentType) 216 | } 217 | if w.Body.Len() == 0 { 218 | t.Error("no response content") 219 | } 220 | 221 | if t.Failed() { 222 | t.FailNow() 223 | } 224 | 225 | respObj, respErr, err := ClientSideSerializer.RespDeserializer.DeserializeResponse(ec, c, w.Result()) 226 | if err != nil { 227 | t.Errorf("DeserializeResponse failed :: %v", err) 228 | t.FailNow() 229 | } 230 | if respErr != nil { 231 | t.Errorf("unexpected respErr :: %v", respErr) 232 | } 233 | if reflect.TypeOf(respObj) != reflect.PtrTo(ec.RespType) { 234 | t.Errorf("deserialised resp type == %v, want %v", reflect.TypeOf(respObj), 235 | reflect.PtrTo(ec.RespType)) 236 | } 237 | resp := respObj.(*RespWithContent) 238 | if inputResp.B0 != resp.B0 || inputResp.B1 != resp.B1 { 239 | t.Errorf("desrialised resp == %#v, want %#v", resp, inputResp) 240 | } 241 | } 242 | 243 | func testErrorResponseSerialization(t *testing.T, e error, status int) { 244 | c := newCtx() 245 | ec := endpointConfigNoContent 246 | w := httptest.NewRecorder() 247 | r := httptest.NewRequest(ec.Method, ec.Path, nil) 248 | err := ServerSideSerializer.SerializeResponse(ec, c, w, r, nil, e) 249 | if err != nil { 250 | t.Errorf("SerializeResponse failed :: %v", err) 251 | t.FailNow() 252 | } 253 | 254 | if w.Code != status { 255 | t.Errorf("response status code %v, want %v", w.Code, status) 256 | } 257 | if v := w.Header().Get("Content-Type"); v != jsonContentType { 258 | t.Errorf("Content-Type header == %q, want %q", v, jsonContentType) 259 | } 260 | if w.Body.Len() == 0 { 261 | t.Error("no content") 262 | } 263 | 264 | if t.Failed() { 265 | t.FailNow() 266 | } 267 | 268 | respObj, respErr, err := ClientSideSerializer.RespDeserializer.DeserializeResponse(ec, c, w.Result()) 269 | if err != nil { 270 | t.Errorf("DeserializeResponse failed :: %v", err) 271 | t.FailNow() 272 | } 273 | if respObj != nil { 274 | t.Errorf("unexpected respObj == %#v, want nil", respObj) 275 | } 276 | if respErr == nil { 277 | t.Error("respErr is nil") 278 | t.FailNow() 279 | } 280 | if respErr.Error() != e.Error() { 281 | t.Errorf("error message == %q, want %q", respErr.Error(), e.Error()) 282 | } 283 | errCode := util.GetErrCode(e) 284 | if v := util.GetErrCode(respErr); v != errCode { 285 | t.Errorf("error code == %q, want %q", v, errCode) 286 | } 287 | } 288 | 289 | func TestRespSerialization_NonUtilError(t *testing.T) { 290 | testErrorResponseSerialization(t, errors.New("test error"), 500) 291 | } 292 | 293 | func TestRespSerialization_ClientUtilError(t *testing.T) { 294 | // Since the error code has a "C-" prefix it will be treated as a client 295 | // error so the http status will be 400 instead of 500. 296 | e := util.ErrCode(nil, "C-WHATEVER", "test error") 297 | testErrorResponseSerialization(t, e, 400) 298 | } 299 | 300 | func TestRespSerialization_ServerUtilError(t *testing.T) { 301 | // Error code prefixes other than "C-" should result in 500. 302 | e := util.ErrCode(nil, "S-WHATEVER", "test error") 303 | testErrorResponseSerialization(t, e, 500) 304 | } 305 | 306 | func TestRespSerialization_NotFoundUtilError(t *testing.T) { 307 | // The config.ErrorCodeNotFound is handled by the http transport 308 | // implementation specially and it results in http status 404. 309 | e := util.ErrCode(nil, config.ErrorCodeNotFound, "test error") 310 | testErrorResponseSerialization(t, e, 404) 311 | } 312 | -------------------------------------------------------------------------------- /addons/transport/http/serialization/serializer.go: -------------------------------------------------------------------------------- 1 | package serialization 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/pasztorpisti/nano" 7 | "github.com/pasztorpisti/nano/addons/transport/http/config" 8 | ) 9 | 10 | type ReqSerializer interface { 11 | SerializeRequest(ec *config.EndpointConfig, c *nano.Ctx, req interface{}, 12 | ) (h http.Header, body []byte, err error) 13 | } 14 | 15 | type ReqInfo struct { 16 | ReqID string 17 | ClientName string 18 | } 19 | 20 | type ReqDeserializer interface { 21 | DeserializeRequest(ec *config.EndpointConfig, r *http.Request, 22 | ) (req interface{}, ri ReqInfo, err error) 23 | } 24 | 25 | type RespSerializer interface { 26 | // c might be nil if errResp!=nil. 27 | SerializeResponse(ec *config.EndpointConfig, c *nano.Ctx, w http.ResponseWriter, 28 | r *http.Request, resp interface{}, errResp error) error 29 | } 30 | 31 | type RespDeserializer interface { 32 | DeserializeResponse(ec *config.EndpointConfig, c *nano.Ctx, resp *http.Response, 33 | ) (respObj interface{}, respErr error, err error) 34 | } 35 | 36 | type ClientSideSerializer struct { 37 | ReqSerializer 38 | RespDeserializer 39 | } 40 | 41 | type ServerSideSerializer struct { 42 | ReqDeserializer 43 | RespSerializer 44 | } 45 | -------------------------------------------------------------------------------- /addons/util/func_service.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "github.com/pasztorpisti/nano" 4 | 5 | // HandlerFunc is a function type compatible with the Service.Handle interface 6 | // method. 7 | type HandlerFunc func(c *nano.Ctx, req interface{}) (resp interface{}, err error) 8 | 9 | // ServiceOpts is used as an incoming parameter for the NewServiceOpts function. 10 | type ServiceOpts struct { 11 | // Name is the name of the service object to create. 12 | Name string 13 | 14 | // Handler is the handler function of the service object to create. 15 | Handler HandlerFunc 16 | 17 | // Init contains code for the Init method of the service object. Can be nil. 18 | Init func(cs nano.ClientSet) error 19 | 20 | // InitFinished contains code for the InitFinished method of the service 21 | // object. Can be nil. 22 | InitFinished func() error 23 | } 24 | 25 | // NewService creates a service object from the given service name and handler 26 | // function. This function is useful for creating simple mock service objects 27 | // inside test functions but its usage isn't restricted to tests. 28 | func NewService(name string, handler HandlerFunc) nano.Service { 29 | return NewServiceOpts(ServiceOpts{ 30 | Name: name, 31 | Handler: handler, 32 | }) 33 | } 34 | 35 | // NewServiceOpts creates a service object from the given service name and 36 | // handler function(s). This function is useful for creating simple mock service 37 | // objects inside test functions but its usage isn't restricted to tests. 38 | func NewServiceOpts(opts ServiceOpts) nano.Service { 39 | if opts.Name == "" { 40 | panic("name is an empty string") 41 | } 42 | if opts.Handler == nil { 43 | panic("handle is nil") 44 | } 45 | 46 | svc := service{ 47 | name: opts.Name, 48 | handler: opts.Handler, 49 | } 50 | 51 | switch { 52 | case opts.Init != nil && opts.InitFinished != nil: 53 | return &struct { 54 | service 55 | serviceInit 56 | serviceInitFinished 57 | }{ 58 | service: svc, 59 | serviceInit: opts.Init, 60 | serviceInitFinished: opts.InitFinished, 61 | } 62 | case opts.Init != nil: 63 | return &struct { 64 | service 65 | serviceInit 66 | }{ 67 | service: svc, 68 | serviceInit: opts.Init, 69 | } 70 | case opts.InitFinished != nil: 71 | return &struct { 72 | service 73 | serviceInitFinished 74 | }{ 75 | service: svc, 76 | serviceInitFinished: opts.InitFinished, 77 | } 78 | default: 79 | return &svc 80 | } 81 | } 82 | 83 | // service implements the nano.Service interface. 84 | type service struct { 85 | name string 86 | handler HandlerFunc 87 | } 88 | 89 | func (p *service) Name() string { 90 | return p.name 91 | } 92 | 93 | func (p *service) Handle(c *nano.Ctx, req interface{}) (resp interface{}, err error) { 94 | return p.handler(c, req) 95 | } 96 | 97 | // serviceInit implements the nano.ServiceInit interface. 98 | type serviceInit func(cs nano.ClientSet) error 99 | 100 | func (f serviceInit) Init(cs nano.ClientSet) error { 101 | return f(cs) 102 | } 103 | 104 | // serviceInitFinished implements the nano.ServiceInitFinished interface. 105 | type serviceInitFinished func() error 106 | 107 | func (f serviceInitFinished) InitFinished() error { 108 | return f() 109 | } 110 | -------------------------------------------------------------------------------- /addons/util/func_service_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/pasztorpisti/nano" 8 | ) 9 | 10 | var handlerError = errors.New("handler") 11 | var initError = errors.New("init") 12 | var initFinishedError = errors.New("initFinished") 13 | 14 | func emptyHandler(c *nano.Ctx, req interface{}) (resp interface{}, err error) { 15 | return nil, handlerError 16 | } 17 | 18 | func emptyInit(cs nano.ClientSet) error { 19 | return initError 20 | } 21 | 22 | func emptyInitFinished() error { 23 | return initFinishedError 24 | } 25 | 26 | func TestNewService(t *testing.T) { 27 | svc := NewService("svc", emptyHandler) 28 | 29 | if _, err := svc.Handle(nil, nil); err != handlerError { 30 | t.Errorf("svc.Handle returned an unexpected error: %v", err) 31 | } 32 | 33 | if _, ok := svc.(nano.ServiceInit); ok { 34 | t.Error("svc implements nano.ServiceInit") 35 | } 36 | 37 | if _, ok := svc.(nano.ServiceInitFinished); ok { 38 | t.Error("svc implements nano.ServiceInitFinished") 39 | } 40 | } 41 | 42 | func TestNewServiceOpts(t *testing.T) { 43 | svc := NewServiceOpts(ServiceOpts{ 44 | Name: "svc", 45 | Handler: emptyHandler, 46 | }) 47 | 48 | if _, err := svc.Handle(nil, nil); err != handlerError { 49 | t.Errorf("svc.Handle returned an unexpected error: %v", err) 50 | } 51 | 52 | if _, ok := svc.(nano.ServiceInit); ok { 53 | t.Error("svc implements nano.ServiceInit") 54 | } 55 | 56 | if _, ok := svc.(nano.ServiceInitFinished); ok { 57 | t.Error("svc implements nano.ServiceInitFinished") 58 | } 59 | } 60 | 61 | func TestNewServiceOpts_Init(t *testing.T) { 62 | svc := NewServiceOpts(ServiceOpts{ 63 | Name: "svc", 64 | Handler: emptyHandler, 65 | Init: emptyInit, 66 | }) 67 | 68 | if _, err := svc.Handle(nil, nil); err != handlerError { 69 | t.Errorf("svc.Handle returned an unexpected error: %v", err) 70 | } 71 | 72 | if svcInit, ok := svc.(nano.ServiceInit); ok { 73 | if err := svcInit.Init(nil); err != initError { 74 | t.Errorf("svc.Init returned an unexpected error: %v", err) 75 | } 76 | } else { 77 | t.Error("svc doesn't implement nano.ServiceInit") 78 | } 79 | 80 | if _, ok := svc.(nano.ServiceInitFinished); ok { 81 | t.Error("svc implements nano.ServiceInitFinished") 82 | } 83 | } 84 | 85 | func TestNewServiceOpts_InitFinished(t *testing.T) { 86 | svc := NewServiceOpts(ServiceOpts{ 87 | Name: "svc", 88 | Handler: emptyHandler, 89 | InitFinished: emptyInitFinished, 90 | }) 91 | 92 | if _, err := svc.Handle(nil, nil); err != handlerError { 93 | t.Errorf("svc.Handle returned an unexpected error: %v", err) 94 | } 95 | 96 | if _, ok := svc.(nano.ServiceInit); ok { 97 | t.Error("svc implements nano.ServiceInit") 98 | } 99 | 100 | if svcInitFinished, ok := svc.(nano.ServiceInitFinished); ok { 101 | if err := svcInitFinished.InitFinished(); err != initFinishedError { 102 | t.Errorf("svc.InitFinished returned an unexpected error: %v", err) 103 | } 104 | } else { 105 | t.Error("svc doesn't implement nano.ServiceInitFinished") 106 | } 107 | } 108 | 109 | func TestNewServiceOpts_Init_And_InitFinished(t *testing.T) { 110 | svc := NewServiceOpts(ServiceOpts{ 111 | Name: "svc", 112 | Handler: emptyHandler, 113 | Init: emptyInit, 114 | InitFinished: emptyInitFinished, 115 | }) 116 | 117 | if _, err := svc.Handle(nil, nil); err != handlerError { 118 | t.Errorf("svc.Handle returned an unexpected error: %v", err) 119 | } 120 | 121 | if svcInit, ok := svc.(nano.ServiceInit); ok { 122 | if err := svcInit.Init(nil); err != initError { 123 | t.Errorf("svc.Init returned an unexpected error: %v", err) 124 | } 125 | } else { 126 | t.Error("svc doesn't implement nano.ServiceInit") 127 | } 128 | 129 | if svcInitFinished, ok := svc.(nano.ServiceInitFinished); ok { 130 | if err := svcInitFinished.InitFinished(); err != initFinishedError { 131 | t.Errorf("svc.InitFinished returned an unexpected error: %v", err) 132 | } 133 | } else { 134 | t.Error("svc doesn't implement nano.ServiceInitFinished") 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /addons/util/nano_error.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "fmt" 4 | 5 | type NanoError interface { 6 | error 7 | Code() string 8 | } 9 | 10 | var ErrMsgChainSeparator = " :: " 11 | 12 | func ErrCode(cause error, code, msg string) NanoError { 13 | e := &nanoError{ 14 | cause: cause, 15 | code: code, 16 | msg: msg, 17 | } 18 | if e.code == "" && cause != nil { 19 | e.code = GetErrCode(cause) 20 | } 21 | return e 22 | } 23 | 24 | func ErrCodef(cause error, code, format string, a ...interface{}) NanoError { 25 | return ErrCode(cause, code, fmt.Sprintf(format, a)) 26 | } 27 | 28 | func Err(cause error, msg string) NanoError { 29 | return ErrCode(cause, "", msg) 30 | } 31 | 32 | func Errf(cause error, format string, a ...interface{}) NanoError { 33 | return ErrCodef(cause, "", format, a) 34 | } 35 | 36 | func GetErrCode(err error) string { 37 | if e, ok := err.(NanoError); ok { 38 | return e.Code() 39 | } 40 | return "" 41 | } 42 | 43 | type nanoError struct { 44 | cause error 45 | code string 46 | msg string 47 | } 48 | 49 | func (p *nanoError) Error() string { 50 | msg := p.msg 51 | if p.cause != nil { 52 | msg += ErrMsgChainSeparator + p.cause.Error() 53 | } 54 | return msg 55 | } 56 | 57 | func (p *nanoError) Code() string { 58 | return p.code 59 | } 60 | -------------------------------------------------------------------------------- /examples/example1/api/svc1/http_transport_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_name": "svc1", 3 | "endpoints": [ 4 | { 5 | "method": "POST", 6 | "path": "/", 7 | "has_req_content": true, 8 | "req_type": "Req", 9 | "resp_type": "Resp" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /examples/example1/api/svc1/requests.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package svc1; 4 | 5 | message Req { 6 | string param = 1; 7 | } 8 | 9 | message Resp { 10 | string value = 1; 11 | } 12 | -------------------------------------------------------------------------------- /examples/example1/api/svc2/http_transport_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_name": "svc2", 3 | "endpoints": [ 4 | { 5 | "method": "POST", 6 | "path": "/", 7 | "has_req_content": true, 8 | "req_type": "Req", 9 | "resp_type": "Resp" 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/", 14 | "has_req_content": false, 15 | "req_type": "GetReq", 16 | "resp_type": "GetResp" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /examples/example1/api/svc2/requests.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package svc2; 4 | 5 | message Req { 6 | string param = 1; 7 | } 8 | 9 | message Resp { 10 | string value = 1; 11 | } 12 | 13 | 14 | message GetReq { 15 | } 16 | 17 | message GetResp { 18 | string value = 1; 19 | } 20 | -------------------------------------------------------------------------------- /examples/example1/api/svc3/http_transport_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_name": "svc3", 3 | "endpoints": [ 4 | { 5 | "method": "POST", 6 | "path": "/", 7 | "has_req_content": true, 8 | "req_type": "Req", 9 | "resp_type": "Resp" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /examples/example1/api/svc3/requests.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package svc3; 4 | 5 | message Req { 6 | string param = 1; 7 | } 8 | 9 | message Resp { 10 | string value = 1; 11 | } 12 | -------------------------------------------------------------------------------- /examples/example1/api/svc4/http_transport_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "service_name": "svc4", 3 | "endpoints": [ 4 | { 5 | "method": "POST", 6 | "path": "/", 7 | "has_req_content": true, 8 | "req_type": "Req", 9 | "resp_type": "Resp" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /examples/example1/api/svc4/requests.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package svc4; 4 | 5 | message Req { 6 | string param = 1; 7 | } 8 | 9 | message Resp { 10 | string value = 1; 11 | } 12 | -------------------------------------------------------------------------------- /examples/example1/api_go/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This scripts generates api_go from the the language independent ../api dir. 4 | # Requires protoc and go installations and the GOPATH env var to be set. 5 | # 6 | 7 | set -euo pipefail 8 | IFS=$'\n' 9 | 10 | cd "$( dirname "$0" )" 11 | THIS_DIR="$( pwd )" 12 | 13 | INPUT_ROOT_PKG="github.com/pasztorpisti/nano/examples/example1/api" 14 | OUTPUT_ROOT_PKG="github.com/pasztorpisti/nano/examples/example1/api_go" 15 | 16 | INPUT_ROOT_DIR="${GOPATH}/src/${INPUT_ROOT_PKG}" 17 | OUTPUT_ROOT_DIR="${GOPATH}/src/${OUTPUT_ROOT_PKG}" 18 | 19 | function main() { 20 | initialise 21 | 22 | if [[ $# -ge 1 ]]; then 23 | local SERVICES=( "$@" ) 24 | else 25 | local SERVICES=() 26 | local DIR 27 | for DIR in $( find "${INPUT_ROOT_DIR}" -type d -depth 1 ); do 28 | SERVICES+=( "$( basename "${DIR}" )" ) 29 | done 30 | fi 31 | 32 | local SERVICE 33 | for SERVICE in "${SERVICES[@]}"; do 34 | generate_service "${SERVICE}" 35 | done 36 | } 37 | 38 | function initialise() { 39 | if ! command_exists protoc; then 40 | >&2 echo "Please install the protoc command and put it onto your PATH." 41 | exit 1 42 | fi 43 | if ! command_exists go; then 44 | >&2 echo "Please install go and put it onto your PATH." 45 | exit 1 46 | fi 47 | 48 | # With bash 4.2 and newer we could use -v to check if the env var is set 49 | # but some distros have quite old bash and OSX has a 10 years old version! 50 | if [[ -z ${GOPATH+x} ]]; then 51 | >&2 echo "Please set the GOPATH env var." 52 | exit 1 53 | fi 54 | export PATH="${PATH}:${GOPATH}/bin" 55 | 56 | if ! command_exists gen_http_transport_config; then 57 | go install "github.com/pasztorpisti/nano/addons/transport/http/config/gen_http_transport_config" 58 | fi 59 | if ! command_exists protoc-gen-gogofaster; then 60 | go install "github.com/gogo/protobuf/protoc-gen-gogofaster" 61 | fi 62 | } 63 | 64 | function command_exists() { 65 | type "$1" >&/dev/null 66 | } 67 | 68 | function generate_service() { 69 | local SVC="$1" 70 | 71 | local INPUT_PKG="${INPUT_ROOT_PKG}/${SVC}" 72 | local OUTPUT_PKG="${OUTPUT_ROOT_PKG}/${SVC}" 73 | 74 | local INPUT_DIR="${INPUT_ROOT_DIR}/${SVC}" 75 | local OUTPUT_DIR="${OUTPUT_ROOT_DIR}/${SVC}" 76 | 77 | if [[ ! -d "${INPUT_DIR}" ]]; then 78 | >&2 echo "Input dir doesn't exist: ${INPUT_DIR}" 79 | return 1 80 | fi 81 | 82 | mkdir -p "${OUTPUT_DIR}" 83 | 84 | if [[ -r "${INPUT_DIR}/requests.proto" ]]; then 85 | echo "Processing ${INPUT_DIR}/requests.proto ..." 86 | local IMPORT_MAP=( 87 | "M${INPUT_PKG}/requests.proto=${OUTPUT_PKG}" 88 | ) 89 | protoc \ 90 | --proto_path="${INPUT_DIR}" \ 91 | --gogofaster_out="$( IFS="," && echo "${IMPORT_MAP[*]}" ):${OUTPUT_DIR}" \ 92 | "${INPUT_DIR}/requests.proto" 93 | fi 94 | 95 | if [[ -r "${INPUT_DIR}/http_transport_config.json" ]]; then 96 | echo "Processing ${INPUT_DIR}/http_transport_config.json ..." 97 | gen_http_transport_config \ 98 | "${INPUT_DIR}/http_transport_config.json:${OUTPUT_DIR}/http_transport_config.go" 99 | gofmt -w "${OUTPUT_DIR}/http_transport_config.go" 100 | fi 101 | } 102 | 103 | main "$@" 104 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc1/http_transport_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | DO NOT EDIT! 3 | This file has been generated from JSON by gen_http_transport_config. 4 | */ 5 | package svc1 6 | 7 | import ( 8 | "reflect" 9 | 10 | "github.com/pasztorpisti/nano/addons/transport/http/config" 11 | ) 12 | 13 | var HTTPTransportConfig = &config.ServiceConfig{ 14 | ServiceName: "svc1", 15 | Endpoints: []*config.EndpointConfig{ 16 | { 17 | Method: "POST", 18 | Path: "/", 19 | HasReqContent: true, 20 | ReqType: reflect.TypeOf((*Req)(nil)).Elem(), 21 | RespType: reflect.TypeOf((*Resp)(nil)).Elem(), 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc1/requests.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. 2 | // source: requests.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package svc1 is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | requests.proto 10 | 11 | It has these top-level messages: 12 | Req 13 | Resp 14 | */ 15 | package svc1 16 | 17 | import proto "github.com/gogo/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | import io "io" 22 | 23 | // Reference imports to suppress errors if they are not otherwise used. 24 | var _ = proto.Marshal 25 | var _ = fmt.Errorf 26 | var _ = math.Inf 27 | 28 | // This is a compile-time assertion to ensure that this generated file 29 | // is compatible with the proto package it is being compiled against. 30 | // A compilation error at this line likely means your copy of the 31 | // proto package needs to be updated. 32 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 33 | 34 | type Req struct { 35 | Param string `protobuf:"bytes,1,opt,name=param,proto3" json:"param,omitempty"` 36 | } 37 | 38 | func (m *Req) Reset() { *m = Req{} } 39 | func (m *Req) String() string { return proto.CompactTextString(m) } 40 | func (*Req) ProtoMessage() {} 41 | func (*Req) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{0} } 42 | 43 | func (m *Req) GetParam() string { 44 | if m != nil { 45 | return m.Param 46 | } 47 | return "" 48 | } 49 | 50 | type Resp struct { 51 | Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` 52 | } 53 | 54 | func (m *Resp) Reset() { *m = Resp{} } 55 | func (m *Resp) String() string { return proto.CompactTextString(m) } 56 | func (*Resp) ProtoMessage() {} 57 | func (*Resp) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{1} } 58 | 59 | func (m *Resp) GetValue() string { 60 | if m != nil { 61 | return m.Value 62 | } 63 | return "" 64 | } 65 | 66 | func init() { 67 | proto.RegisterType((*Req)(nil), "svc1.Req") 68 | proto.RegisterType((*Resp)(nil), "svc1.Resp") 69 | } 70 | func (m *Req) Marshal() (dAtA []byte, err error) { 71 | size := m.Size() 72 | dAtA = make([]byte, size) 73 | n, err := m.MarshalTo(dAtA) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return dAtA[:n], nil 78 | } 79 | 80 | func (m *Req) MarshalTo(dAtA []byte) (int, error) { 81 | var i int 82 | _ = i 83 | var l int 84 | _ = l 85 | if len(m.Param) > 0 { 86 | dAtA[i] = 0xa 87 | i++ 88 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Param))) 89 | i += copy(dAtA[i:], m.Param) 90 | } 91 | return i, nil 92 | } 93 | 94 | func (m *Resp) Marshal() (dAtA []byte, err error) { 95 | size := m.Size() 96 | dAtA = make([]byte, size) 97 | n, err := m.MarshalTo(dAtA) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return dAtA[:n], nil 102 | } 103 | 104 | func (m *Resp) MarshalTo(dAtA []byte) (int, error) { 105 | var i int 106 | _ = i 107 | var l int 108 | _ = l 109 | if len(m.Value) > 0 { 110 | dAtA[i] = 0xa 111 | i++ 112 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Value))) 113 | i += copy(dAtA[i:], m.Value) 114 | } 115 | return i, nil 116 | } 117 | 118 | func encodeFixed64Requests(dAtA []byte, offset int, v uint64) int { 119 | dAtA[offset] = uint8(v) 120 | dAtA[offset+1] = uint8(v >> 8) 121 | dAtA[offset+2] = uint8(v >> 16) 122 | dAtA[offset+3] = uint8(v >> 24) 123 | dAtA[offset+4] = uint8(v >> 32) 124 | dAtA[offset+5] = uint8(v >> 40) 125 | dAtA[offset+6] = uint8(v >> 48) 126 | dAtA[offset+7] = uint8(v >> 56) 127 | return offset + 8 128 | } 129 | func encodeFixed32Requests(dAtA []byte, offset int, v uint32) int { 130 | dAtA[offset] = uint8(v) 131 | dAtA[offset+1] = uint8(v >> 8) 132 | dAtA[offset+2] = uint8(v >> 16) 133 | dAtA[offset+3] = uint8(v >> 24) 134 | return offset + 4 135 | } 136 | func encodeVarintRequests(dAtA []byte, offset int, v uint64) int { 137 | for v >= 1<<7 { 138 | dAtA[offset] = uint8(v&0x7f | 0x80) 139 | v >>= 7 140 | offset++ 141 | } 142 | dAtA[offset] = uint8(v) 143 | return offset + 1 144 | } 145 | func (m *Req) Size() (n int) { 146 | var l int 147 | _ = l 148 | l = len(m.Param) 149 | if l > 0 { 150 | n += 1 + l + sovRequests(uint64(l)) 151 | } 152 | return n 153 | } 154 | 155 | func (m *Resp) Size() (n int) { 156 | var l int 157 | _ = l 158 | l = len(m.Value) 159 | if l > 0 { 160 | n += 1 + l + sovRequests(uint64(l)) 161 | } 162 | return n 163 | } 164 | 165 | func sovRequests(x uint64) (n int) { 166 | for { 167 | n++ 168 | x >>= 7 169 | if x == 0 { 170 | break 171 | } 172 | } 173 | return n 174 | } 175 | func sozRequests(x uint64) (n int) { 176 | return sovRequests(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 177 | } 178 | func (m *Req) Unmarshal(dAtA []byte) error { 179 | l := len(dAtA) 180 | iNdEx := 0 181 | for iNdEx < l { 182 | preIndex := iNdEx 183 | var wire uint64 184 | for shift := uint(0); ; shift += 7 { 185 | if shift >= 64 { 186 | return ErrIntOverflowRequests 187 | } 188 | if iNdEx >= l { 189 | return io.ErrUnexpectedEOF 190 | } 191 | b := dAtA[iNdEx] 192 | iNdEx++ 193 | wire |= (uint64(b) & 0x7F) << shift 194 | if b < 0x80 { 195 | break 196 | } 197 | } 198 | fieldNum := int32(wire >> 3) 199 | wireType := int(wire & 0x7) 200 | if wireType == 4 { 201 | return fmt.Errorf("proto: Req: wiretype end group for non-group") 202 | } 203 | if fieldNum <= 0 { 204 | return fmt.Errorf("proto: Req: illegal tag %d (wire type %d)", fieldNum, wire) 205 | } 206 | switch fieldNum { 207 | case 1: 208 | if wireType != 2 { 209 | return fmt.Errorf("proto: wrong wireType = %d for field Param", wireType) 210 | } 211 | var stringLen uint64 212 | for shift := uint(0); ; shift += 7 { 213 | if shift >= 64 { 214 | return ErrIntOverflowRequests 215 | } 216 | if iNdEx >= l { 217 | return io.ErrUnexpectedEOF 218 | } 219 | b := dAtA[iNdEx] 220 | iNdEx++ 221 | stringLen |= (uint64(b) & 0x7F) << shift 222 | if b < 0x80 { 223 | break 224 | } 225 | } 226 | intStringLen := int(stringLen) 227 | if intStringLen < 0 { 228 | return ErrInvalidLengthRequests 229 | } 230 | postIndex := iNdEx + intStringLen 231 | if postIndex > l { 232 | return io.ErrUnexpectedEOF 233 | } 234 | m.Param = string(dAtA[iNdEx:postIndex]) 235 | iNdEx = postIndex 236 | default: 237 | iNdEx = preIndex 238 | skippy, err := skipRequests(dAtA[iNdEx:]) 239 | if err != nil { 240 | return err 241 | } 242 | if skippy < 0 { 243 | return ErrInvalidLengthRequests 244 | } 245 | if (iNdEx + skippy) > l { 246 | return io.ErrUnexpectedEOF 247 | } 248 | iNdEx += skippy 249 | } 250 | } 251 | 252 | if iNdEx > l { 253 | return io.ErrUnexpectedEOF 254 | } 255 | return nil 256 | } 257 | func (m *Resp) Unmarshal(dAtA []byte) error { 258 | l := len(dAtA) 259 | iNdEx := 0 260 | for iNdEx < l { 261 | preIndex := iNdEx 262 | var wire uint64 263 | for shift := uint(0); ; shift += 7 { 264 | if shift >= 64 { 265 | return ErrIntOverflowRequests 266 | } 267 | if iNdEx >= l { 268 | return io.ErrUnexpectedEOF 269 | } 270 | b := dAtA[iNdEx] 271 | iNdEx++ 272 | wire |= (uint64(b) & 0x7F) << shift 273 | if b < 0x80 { 274 | break 275 | } 276 | } 277 | fieldNum := int32(wire >> 3) 278 | wireType := int(wire & 0x7) 279 | if wireType == 4 { 280 | return fmt.Errorf("proto: Resp: wiretype end group for non-group") 281 | } 282 | if fieldNum <= 0 { 283 | return fmt.Errorf("proto: Resp: illegal tag %d (wire type %d)", fieldNum, wire) 284 | } 285 | switch fieldNum { 286 | case 1: 287 | if wireType != 2 { 288 | return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) 289 | } 290 | var stringLen uint64 291 | for shift := uint(0); ; shift += 7 { 292 | if shift >= 64 { 293 | return ErrIntOverflowRequests 294 | } 295 | if iNdEx >= l { 296 | return io.ErrUnexpectedEOF 297 | } 298 | b := dAtA[iNdEx] 299 | iNdEx++ 300 | stringLen |= (uint64(b) & 0x7F) << shift 301 | if b < 0x80 { 302 | break 303 | } 304 | } 305 | intStringLen := int(stringLen) 306 | if intStringLen < 0 { 307 | return ErrInvalidLengthRequests 308 | } 309 | postIndex := iNdEx + intStringLen 310 | if postIndex > l { 311 | return io.ErrUnexpectedEOF 312 | } 313 | m.Value = string(dAtA[iNdEx:postIndex]) 314 | iNdEx = postIndex 315 | default: 316 | iNdEx = preIndex 317 | skippy, err := skipRequests(dAtA[iNdEx:]) 318 | if err != nil { 319 | return err 320 | } 321 | if skippy < 0 { 322 | return ErrInvalidLengthRequests 323 | } 324 | if (iNdEx + skippy) > l { 325 | return io.ErrUnexpectedEOF 326 | } 327 | iNdEx += skippy 328 | } 329 | } 330 | 331 | if iNdEx > l { 332 | return io.ErrUnexpectedEOF 333 | } 334 | return nil 335 | } 336 | func skipRequests(dAtA []byte) (n int, err error) { 337 | l := len(dAtA) 338 | iNdEx := 0 339 | for iNdEx < l { 340 | var wire uint64 341 | for shift := uint(0); ; shift += 7 { 342 | if shift >= 64 { 343 | return 0, ErrIntOverflowRequests 344 | } 345 | if iNdEx >= l { 346 | return 0, io.ErrUnexpectedEOF 347 | } 348 | b := dAtA[iNdEx] 349 | iNdEx++ 350 | wire |= (uint64(b) & 0x7F) << shift 351 | if b < 0x80 { 352 | break 353 | } 354 | } 355 | wireType := int(wire & 0x7) 356 | switch wireType { 357 | case 0: 358 | for shift := uint(0); ; shift += 7 { 359 | if shift >= 64 { 360 | return 0, ErrIntOverflowRequests 361 | } 362 | if iNdEx >= l { 363 | return 0, io.ErrUnexpectedEOF 364 | } 365 | iNdEx++ 366 | if dAtA[iNdEx-1] < 0x80 { 367 | break 368 | } 369 | } 370 | return iNdEx, nil 371 | case 1: 372 | iNdEx += 8 373 | return iNdEx, nil 374 | case 2: 375 | var length int 376 | for shift := uint(0); ; shift += 7 { 377 | if shift >= 64 { 378 | return 0, ErrIntOverflowRequests 379 | } 380 | if iNdEx >= l { 381 | return 0, io.ErrUnexpectedEOF 382 | } 383 | b := dAtA[iNdEx] 384 | iNdEx++ 385 | length |= (int(b) & 0x7F) << shift 386 | if b < 0x80 { 387 | break 388 | } 389 | } 390 | iNdEx += length 391 | if length < 0 { 392 | return 0, ErrInvalidLengthRequests 393 | } 394 | return iNdEx, nil 395 | case 3: 396 | for { 397 | var innerWire uint64 398 | var start int = iNdEx 399 | for shift := uint(0); ; shift += 7 { 400 | if shift >= 64 { 401 | return 0, ErrIntOverflowRequests 402 | } 403 | if iNdEx >= l { 404 | return 0, io.ErrUnexpectedEOF 405 | } 406 | b := dAtA[iNdEx] 407 | iNdEx++ 408 | innerWire |= (uint64(b) & 0x7F) << shift 409 | if b < 0x80 { 410 | break 411 | } 412 | } 413 | innerWireType := int(innerWire & 0x7) 414 | if innerWireType == 4 { 415 | break 416 | } 417 | next, err := skipRequests(dAtA[start:]) 418 | if err != nil { 419 | return 0, err 420 | } 421 | iNdEx = start + next 422 | } 423 | return iNdEx, nil 424 | case 4: 425 | return iNdEx, nil 426 | case 5: 427 | iNdEx += 4 428 | return iNdEx, nil 429 | default: 430 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 431 | } 432 | } 433 | panic("unreachable") 434 | } 435 | 436 | var ( 437 | ErrInvalidLengthRequests = fmt.Errorf("proto: negative length found during unmarshaling") 438 | ErrIntOverflowRequests = fmt.Errorf("proto: integer overflow") 439 | ) 440 | 441 | func init() { proto.RegisterFile("requests.proto", fileDescriptorRequests) } 442 | 443 | var fileDescriptorRequests = []byte{ 444 | // 113 bytes of a gzipped FileDescriptorProto 445 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2b, 0x4a, 0x2d, 0x2c, 446 | 0x4d, 0x2d, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x29, 0x2e, 0x4b, 0x36, 447 | 0x54, 0x92, 0xe6, 0x62, 0x0e, 0x4a, 0x2d, 0x14, 0x12, 0xe1, 0x62, 0x2d, 0x48, 0x2c, 0x4a, 0xcc, 448 | 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x94, 0x64, 0xb8, 0x58, 0x82, 0x52, 0x8b, 449 | 0x0b, 0x40, 0xb2, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x30, 0x59, 0x30, 0xc7, 0x49, 0xe0, 0xc4, 0x23, 450 | 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0x21, 0x89, 451 | 0x0d, 0x6c, 0xb2, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x35, 0x59, 0x4c, 0x6b, 0x00, 0x00, 452 | 0x00, 453 | } 454 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc2/http_transport_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | DO NOT EDIT! 3 | This file has been generated from JSON by gen_http_transport_config. 4 | */ 5 | package svc2 6 | 7 | import ( 8 | "reflect" 9 | 10 | "github.com/pasztorpisti/nano/addons/transport/http/config" 11 | ) 12 | 13 | var HTTPTransportConfig = &config.ServiceConfig{ 14 | ServiceName: "svc2", 15 | Endpoints: []*config.EndpointConfig{ 16 | { 17 | Method: "POST", 18 | Path: "/", 19 | HasReqContent: true, 20 | ReqType: reflect.TypeOf((*Req)(nil)).Elem(), 21 | RespType: reflect.TypeOf((*Resp)(nil)).Elem(), 22 | }, 23 | { 24 | Method: "GET", 25 | Path: "/", 26 | HasReqContent: false, 27 | ReqType: reflect.TypeOf((*GetReq)(nil)).Elem(), 28 | RespType: reflect.TypeOf((*GetResp)(nil)).Elem(), 29 | }, 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc2/requests.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. 2 | // source: requests.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package svc2 is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | requests.proto 10 | 11 | It has these top-level messages: 12 | Req 13 | Resp 14 | GetReq 15 | GetResp 16 | */ 17 | package svc2 18 | 19 | import proto "github.com/gogo/protobuf/proto" 20 | import fmt "fmt" 21 | import math "math" 22 | 23 | import io "io" 24 | 25 | // Reference imports to suppress errors if they are not otherwise used. 26 | var _ = proto.Marshal 27 | var _ = fmt.Errorf 28 | var _ = math.Inf 29 | 30 | // This is a compile-time assertion to ensure that this generated file 31 | // is compatible with the proto package it is being compiled against. 32 | // A compilation error at this line likely means your copy of the 33 | // proto package needs to be updated. 34 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 35 | 36 | type Req struct { 37 | Param string `protobuf:"bytes,1,opt,name=param,proto3" json:"param,omitempty"` 38 | } 39 | 40 | func (m *Req) Reset() { *m = Req{} } 41 | func (m *Req) String() string { return proto.CompactTextString(m) } 42 | func (*Req) ProtoMessage() {} 43 | func (*Req) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{0} } 44 | 45 | func (m *Req) GetParam() string { 46 | if m != nil { 47 | return m.Param 48 | } 49 | return "" 50 | } 51 | 52 | type Resp struct { 53 | Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` 54 | } 55 | 56 | func (m *Resp) Reset() { *m = Resp{} } 57 | func (m *Resp) String() string { return proto.CompactTextString(m) } 58 | func (*Resp) ProtoMessage() {} 59 | func (*Resp) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{1} } 60 | 61 | func (m *Resp) GetValue() string { 62 | if m != nil { 63 | return m.Value 64 | } 65 | return "" 66 | } 67 | 68 | type GetReq struct { 69 | } 70 | 71 | func (m *GetReq) Reset() { *m = GetReq{} } 72 | func (m *GetReq) String() string { return proto.CompactTextString(m) } 73 | func (*GetReq) ProtoMessage() {} 74 | func (*GetReq) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{2} } 75 | 76 | type GetResp struct { 77 | Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` 78 | } 79 | 80 | func (m *GetResp) Reset() { *m = GetResp{} } 81 | func (m *GetResp) String() string { return proto.CompactTextString(m) } 82 | func (*GetResp) ProtoMessage() {} 83 | func (*GetResp) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{3} } 84 | 85 | func (m *GetResp) GetValue() string { 86 | if m != nil { 87 | return m.Value 88 | } 89 | return "" 90 | } 91 | 92 | func init() { 93 | proto.RegisterType((*Req)(nil), "svc2.Req") 94 | proto.RegisterType((*Resp)(nil), "svc2.Resp") 95 | proto.RegisterType((*GetReq)(nil), "svc2.GetReq") 96 | proto.RegisterType((*GetResp)(nil), "svc2.GetResp") 97 | } 98 | func (m *Req) Marshal() (dAtA []byte, err error) { 99 | size := m.Size() 100 | dAtA = make([]byte, size) 101 | n, err := m.MarshalTo(dAtA) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return dAtA[:n], nil 106 | } 107 | 108 | func (m *Req) MarshalTo(dAtA []byte) (int, error) { 109 | var i int 110 | _ = i 111 | var l int 112 | _ = l 113 | if len(m.Param) > 0 { 114 | dAtA[i] = 0xa 115 | i++ 116 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Param))) 117 | i += copy(dAtA[i:], m.Param) 118 | } 119 | return i, nil 120 | } 121 | 122 | func (m *Resp) Marshal() (dAtA []byte, err error) { 123 | size := m.Size() 124 | dAtA = make([]byte, size) 125 | n, err := m.MarshalTo(dAtA) 126 | if err != nil { 127 | return nil, err 128 | } 129 | return dAtA[:n], nil 130 | } 131 | 132 | func (m *Resp) MarshalTo(dAtA []byte) (int, error) { 133 | var i int 134 | _ = i 135 | var l int 136 | _ = l 137 | if len(m.Value) > 0 { 138 | dAtA[i] = 0xa 139 | i++ 140 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Value))) 141 | i += copy(dAtA[i:], m.Value) 142 | } 143 | return i, nil 144 | } 145 | 146 | func (m *GetReq) Marshal() (dAtA []byte, err error) { 147 | size := m.Size() 148 | dAtA = make([]byte, size) 149 | n, err := m.MarshalTo(dAtA) 150 | if err != nil { 151 | return nil, err 152 | } 153 | return dAtA[:n], nil 154 | } 155 | 156 | func (m *GetReq) MarshalTo(dAtA []byte) (int, error) { 157 | var i int 158 | _ = i 159 | var l int 160 | _ = l 161 | return i, nil 162 | } 163 | 164 | func (m *GetResp) Marshal() (dAtA []byte, err error) { 165 | size := m.Size() 166 | dAtA = make([]byte, size) 167 | n, err := m.MarshalTo(dAtA) 168 | if err != nil { 169 | return nil, err 170 | } 171 | return dAtA[:n], nil 172 | } 173 | 174 | func (m *GetResp) MarshalTo(dAtA []byte) (int, error) { 175 | var i int 176 | _ = i 177 | var l int 178 | _ = l 179 | if len(m.Value) > 0 { 180 | dAtA[i] = 0xa 181 | i++ 182 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Value))) 183 | i += copy(dAtA[i:], m.Value) 184 | } 185 | return i, nil 186 | } 187 | 188 | func encodeFixed64Requests(dAtA []byte, offset int, v uint64) int { 189 | dAtA[offset] = uint8(v) 190 | dAtA[offset+1] = uint8(v >> 8) 191 | dAtA[offset+2] = uint8(v >> 16) 192 | dAtA[offset+3] = uint8(v >> 24) 193 | dAtA[offset+4] = uint8(v >> 32) 194 | dAtA[offset+5] = uint8(v >> 40) 195 | dAtA[offset+6] = uint8(v >> 48) 196 | dAtA[offset+7] = uint8(v >> 56) 197 | return offset + 8 198 | } 199 | func encodeFixed32Requests(dAtA []byte, offset int, v uint32) int { 200 | dAtA[offset] = uint8(v) 201 | dAtA[offset+1] = uint8(v >> 8) 202 | dAtA[offset+2] = uint8(v >> 16) 203 | dAtA[offset+3] = uint8(v >> 24) 204 | return offset + 4 205 | } 206 | func encodeVarintRequests(dAtA []byte, offset int, v uint64) int { 207 | for v >= 1<<7 { 208 | dAtA[offset] = uint8(v&0x7f | 0x80) 209 | v >>= 7 210 | offset++ 211 | } 212 | dAtA[offset] = uint8(v) 213 | return offset + 1 214 | } 215 | func (m *Req) Size() (n int) { 216 | var l int 217 | _ = l 218 | l = len(m.Param) 219 | if l > 0 { 220 | n += 1 + l + sovRequests(uint64(l)) 221 | } 222 | return n 223 | } 224 | 225 | func (m *Resp) Size() (n int) { 226 | var l int 227 | _ = l 228 | l = len(m.Value) 229 | if l > 0 { 230 | n += 1 + l + sovRequests(uint64(l)) 231 | } 232 | return n 233 | } 234 | 235 | func (m *GetReq) Size() (n int) { 236 | var l int 237 | _ = l 238 | return n 239 | } 240 | 241 | func (m *GetResp) Size() (n int) { 242 | var l int 243 | _ = l 244 | l = len(m.Value) 245 | if l > 0 { 246 | n += 1 + l + sovRequests(uint64(l)) 247 | } 248 | return n 249 | } 250 | 251 | func sovRequests(x uint64) (n int) { 252 | for { 253 | n++ 254 | x >>= 7 255 | if x == 0 { 256 | break 257 | } 258 | } 259 | return n 260 | } 261 | func sozRequests(x uint64) (n int) { 262 | return sovRequests(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 263 | } 264 | func (m *Req) Unmarshal(dAtA []byte) error { 265 | l := len(dAtA) 266 | iNdEx := 0 267 | for iNdEx < l { 268 | preIndex := iNdEx 269 | var wire uint64 270 | for shift := uint(0); ; shift += 7 { 271 | if shift >= 64 { 272 | return ErrIntOverflowRequests 273 | } 274 | if iNdEx >= l { 275 | return io.ErrUnexpectedEOF 276 | } 277 | b := dAtA[iNdEx] 278 | iNdEx++ 279 | wire |= (uint64(b) & 0x7F) << shift 280 | if b < 0x80 { 281 | break 282 | } 283 | } 284 | fieldNum := int32(wire >> 3) 285 | wireType := int(wire & 0x7) 286 | if wireType == 4 { 287 | return fmt.Errorf("proto: Req: wiretype end group for non-group") 288 | } 289 | if fieldNum <= 0 { 290 | return fmt.Errorf("proto: Req: illegal tag %d (wire type %d)", fieldNum, wire) 291 | } 292 | switch fieldNum { 293 | case 1: 294 | if wireType != 2 { 295 | return fmt.Errorf("proto: wrong wireType = %d for field Param", wireType) 296 | } 297 | var stringLen uint64 298 | for shift := uint(0); ; shift += 7 { 299 | if shift >= 64 { 300 | return ErrIntOverflowRequests 301 | } 302 | if iNdEx >= l { 303 | return io.ErrUnexpectedEOF 304 | } 305 | b := dAtA[iNdEx] 306 | iNdEx++ 307 | stringLen |= (uint64(b) & 0x7F) << shift 308 | if b < 0x80 { 309 | break 310 | } 311 | } 312 | intStringLen := int(stringLen) 313 | if intStringLen < 0 { 314 | return ErrInvalidLengthRequests 315 | } 316 | postIndex := iNdEx + intStringLen 317 | if postIndex > l { 318 | return io.ErrUnexpectedEOF 319 | } 320 | m.Param = string(dAtA[iNdEx:postIndex]) 321 | iNdEx = postIndex 322 | default: 323 | iNdEx = preIndex 324 | skippy, err := skipRequests(dAtA[iNdEx:]) 325 | if err != nil { 326 | return err 327 | } 328 | if skippy < 0 { 329 | return ErrInvalidLengthRequests 330 | } 331 | if (iNdEx + skippy) > l { 332 | return io.ErrUnexpectedEOF 333 | } 334 | iNdEx += skippy 335 | } 336 | } 337 | 338 | if iNdEx > l { 339 | return io.ErrUnexpectedEOF 340 | } 341 | return nil 342 | } 343 | func (m *Resp) Unmarshal(dAtA []byte) error { 344 | l := len(dAtA) 345 | iNdEx := 0 346 | for iNdEx < l { 347 | preIndex := iNdEx 348 | var wire uint64 349 | for shift := uint(0); ; shift += 7 { 350 | if shift >= 64 { 351 | return ErrIntOverflowRequests 352 | } 353 | if iNdEx >= l { 354 | return io.ErrUnexpectedEOF 355 | } 356 | b := dAtA[iNdEx] 357 | iNdEx++ 358 | wire |= (uint64(b) & 0x7F) << shift 359 | if b < 0x80 { 360 | break 361 | } 362 | } 363 | fieldNum := int32(wire >> 3) 364 | wireType := int(wire & 0x7) 365 | if wireType == 4 { 366 | return fmt.Errorf("proto: Resp: wiretype end group for non-group") 367 | } 368 | if fieldNum <= 0 { 369 | return fmt.Errorf("proto: Resp: illegal tag %d (wire type %d)", fieldNum, wire) 370 | } 371 | switch fieldNum { 372 | case 1: 373 | if wireType != 2 { 374 | return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) 375 | } 376 | var stringLen uint64 377 | for shift := uint(0); ; shift += 7 { 378 | if shift >= 64 { 379 | return ErrIntOverflowRequests 380 | } 381 | if iNdEx >= l { 382 | return io.ErrUnexpectedEOF 383 | } 384 | b := dAtA[iNdEx] 385 | iNdEx++ 386 | stringLen |= (uint64(b) & 0x7F) << shift 387 | if b < 0x80 { 388 | break 389 | } 390 | } 391 | intStringLen := int(stringLen) 392 | if intStringLen < 0 { 393 | return ErrInvalidLengthRequests 394 | } 395 | postIndex := iNdEx + intStringLen 396 | if postIndex > l { 397 | return io.ErrUnexpectedEOF 398 | } 399 | m.Value = string(dAtA[iNdEx:postIndex]) 400 | iNdEx = postIndex 401 | default: 402 | iNdEx = preIndex 403 | skippy, err := skipRequests(dAtA[iNdEx:]) 404 | if err != nil { 405 | return err 406 | } 407 | if skippy < 0 { 408 | return ErrInvalidLengthRequests 409 | } 410 | if (iNdEx + skippy) > l { 411 | return io.ErrUnexpectedEOF 412 | } 413 | iNdEx += skippy 414 | } 415 | } 416 | 417 | if iNdEx > l { 418 | return io.ErrUnexpectedEOF 419 | } 420 | return nil 421 | } 422 | func (m *GetReq) Unmarshal(dAtA []byte) error { 423 | l := len(dAtA) 424 | iNdEx := 0 425 | for iNdEx < l { 426 | preIndex := iNdEx 427 | var wire uint64 428 | for shift := uint(0); ; shift += 7 { 429 | if shift >= 64 { 430 | return ErrIntOverflowRequests 431 | } 432 | if iNdEx >= l { 433 | return io.ErrUnexpectedEOF 434 | } 435 | b := dAtA[iNdEx] 436 | iNdEx++ 437 | wire |= (uint64(b) & 0x7F) << shift 438 | if b < 0x80 { 439 | break 440 | } 441 | } 442 | fieldNum := int32(wire >> 3) 443 | wireType := int(wire & 0x7) 444 | if wireType == 4 { 445 | return fmt.Errorf("proto: GetReq: wiretype end group for non-group") 446 | } 447 | if fieldNum <= 0 { 448 | return fmt.Errorf("proto: GetReq: illegal tag %d (wire type %d)", fieldNum, wire) 449 | } 450 | switch fieldNum { 451 | default: 452 | iNdEx = preIndex 453 | skippy, err := skipRequests(dAtA[iNdEx:]) 454 | if err != nil { 455 | return err 456 | } 457 | if skippy < 0 { 458 | return ErrInvalidLengthRequests 459 | } 460 | if (iNdEx + skippy) > l { 461 | return io.ErrUnexpectedEOF 462 | } 463 | iNdEx += skippy 464 | } 465 | } 466 | 467 | if iNdEx > l { 468 | return io.ErrUnexpectedEOF 469 | } 470 | return nil 471 | } 472 | func (m *GetResp) Unmarshal(dAtA []byte) error { 473 | l := len(dAtA) 474 | iNdEx := 0 475 | for iNdEx < l { 476 | preIndex := iNdEx 477 | var wire uint64 478 | for shift := uint(0); ; shift += 7 { 479 | if shift >= 64 { 480 | return ErrIntOverflowRequests 481 | } 482 | if iNdEx >= l { 483 | return io.ErrUnexpectedEOF 484 | } 485 | b := dAtA[iNdEx] 486 | iNdEx++ 487 | wire |= (uint64(b) & 0x7F) << shift 488 | if b < 0x80 { 489 | break 490 | } 491 | } 492 | fieldNum := int32(wire >> 3) 493 | wireType := int(wire & 0x7) 494 | if wireType == 4 { 495 | return fmt.Errorf("proto: GetResp: wiretype end group for non-group") 496 | } 497 | if fieldNum <= 0 { 498 | return fmt.Errorf("proto: GetResp: illegal tag %d (wire type %d)", fieldNum, wire) 499 | } 500 | switch fieldNum { 501 | case 1: 502 | if wireType != 2 { 503 | return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) 504 | } 505 | var stringLen uint64 506 | for shift := uint(0); ; shift += 7 { 507 | if shift >= 64 { 508 | return ErrIntOverflowRequests 509 | } 510 | if iNdEx >= l { 511 | return io.ErrUnexpectedEOF 512 | } 513 | b := dAtA[iNdEx] 514 | iNdEx++ 515 | stringLen |= (uint64(b) & 0x7F) << shift 516 | if b < 0x80 { 517 | break 518 | } 519 | } 520 | intStringLen := int(stringLen) 521 | if intStringLen < 0 { 522 | return ErrInvalidLengthRequests 523 | } 524 | postIndex := iNdEx + intStringLen 525 | if postIndex > l { 526 | return io.ErrUnexpectedEOF 527 | } 528 | m.Value = string(dAtA[iNdEx:postIndex]) 529 | iNdEx = postIndex 530 | default: 531 | iNdEx = preIndex 532 | skippy, err := skipRequests(dAtA[iNdEx:]) 533 | if err != nil { 534 | return err 535 | } 536 | if skippy < 0 { 537 | return ErrInvalidLengthRequests 538 | } 539 | if (iNdEx + skippy) > l { 540 | return io.ErrUnexpectedEOF 541 | } 542 | iNdEx += skippy 543 | } 544 | } 545 | 546 | if iNdEx > l { 547 | return io.ErrUnexpectedEOF 548 | } 549 | return nil 550 | } 551 | func skipRequests(dAtA []byte) (n int, err error) { 552 | l := len(dAtA) 553 | iNdEx := 0 554 | for iNdEx < l { 555 | var wire uint64 556 | for shift := uint(0); ; shift += 7 { 557 | if shift >= 64 { 558 | return 0, ErrIntOverflowRequests 559 | } 560 | if iNdEx >= l { 561 | return 0, io.ErrUnexpectedEOF 562 | } 563 | b := dAtA[iNdEx] 564 | iNdEx++ 565 | wire |= (uint64(b) & 0x7F) << shift 566 | if b < 0x80 { 567 | break 568 | } 569 | } 570 | wireType := int(wire & 0x7) 571 | switch wireType { 572 | case 0: 573 | for shift := uint(0); ; shift += 7 { 574 | if shift >= 64 { 575 | return 0, ErrIntOverflowRequests 576 | } 577 | if iNdEx >= l { 578 | return 0, io.ErrUnexpectedEOF 579 | } 580 | iNdEx++ 581 | if dAtA[iNdEx-1] < 0x80 { 582 | break 583 | } 584 | } 585 | return iNdEx, nil 586 | case 1: 587 | iNdEx += 8 588 | return iNdEx, nil 589 | case 2: 590 | var length int 591 | for shift := uint(0); ; shift += 7 { 592 | if shift >= 64 { 593 | return 0, ErrIntOverflowRequests 594 | } 595 | if iNdEx >= l { 596 | return 0, io.ErrUnexpectedEOF 597 | } 598 | b := dAtA[iNdEx] 599 | iNdEx++ 600 | length |= (int(b) & 0x7F) << shift 601 | if b < 0x80 { 602 | break 603 | } 604 | } 605 | iNdEx += length 606 | if length < 0 { 607 | return 0, ErrInvalidLengthRequests 608 | } 609 | return iNdEx, nil 610 | case 3: 611 | for { 612 | var innerWire uint64 613 | var start int = iNdEx 614 | for shift := uint(0); ; shift += 7 { 615 | if shift >= 64 { 616 | return 0, ErrIntOverflowRequests 617 | } 618 | if iNdEx >= l { 619 | return 0, io.ErrUnexpectedEOF 620 | } 621 | b := dAtA[iNdEx] 622 | iNdEx++ 623 | innerWire |= (uint64(b) & 0x7F) << shift 624 | if b < 0x80 { 625 | break 626 | } 627 | } 628 | innerWireType := int(innerWire & 0x7) 629 | if innerWireType == 4 { 630 | break 631 | } 632 | next, err := skipRequests(dAtA[start:]) 633 | if err != nil { 634 | return 0, err 635 | } 636 | iNdEx = start + next 637 | } 638 | return iNdEx, nil 639 | case 4: 640 | return iNdEx, nil 641 | case 5: 642 | iNdEx += 4 643 | return iNdEx, nil 644 | default: 645 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 646 | } 647 | } 648 | panic("unreachable") 649 | } 650 | 651 | var ( 652 | ErrInvalidLengthRequests = fmt.Errorf("proto: negative length found during unmarshaling") 653 | ErrIntOverflowRequests = fmt.Errorf("proto: integer overflow") 654 | ) 655 | 656 | func init() { proto.RegisterFile("requests.proto", fileDescriptorRequests) } 657 | 658 | var fileDescriptorRequests = []byte{ 659 | // 132 bytes of a gzipped FileDescriptorProto 660 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2b, 0x4a, 0x2d, 0x2c, 661 | 0x4d, 0x2d, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x29, 0x2e, 0x4b, 0x36, 662 | 0x52, 0x92, 0xe6, 0x62, 0x0e, 0x4a, 0x2d, 0x14, 0x12, 0xe1, 0x62, 0x2d, 0x48, 0x2c, 0x4a, 0xcc, 663 | 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x94, 0x64, 0xb8, 0x58, 0x82, 0x52, 0x8b, 664 | 0x0b, 0x40, 0xb2, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x30, 0x59, 0x30, 0x47, 0x89, 0x83, 0x8b, 0xcd, 665 | 0x3d, 0xb5, 0x24, 0x28, 0xb5, 0x50, 0x49, 0x9e, 0x8b, 0x1d, 0xcc, 0xc2, 0xa5, 0xd4, 0x49, 0xe0, 666 | 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 667 | 0x21, 0x89, 0x0d, 0xec, 0x08, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x8d, 0xd4, 0x70, 668 | 0x96, 0x00, 0x00, 0x00, 669 | } 670 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc3/http_transport_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | DO NOT EDIT! 3 | This file has been generated from JSON by gen_http_transport_config. 4 | */ 5 | package svc3 6 | 7 | import ( 8 | "reflect" 9 | 10 | "github.com/pasztorpisti/nano/addons/transport/http/config" 11 | ) 12 | 13 | var HTTPTransportConfig = &config.ServiceConfig{ 14 | ServiceName: "svc3", 15 | Endpoints: []*config.EndpointConfig{ 16 | { 17 | Method: "POST", 18 | Path: "/", 19 | HasReqContent: true, 20 | ReqType: reflect.TypeOf((*Req)(nil)).Elem(), 21 | RespType: reflect.TypeOf((*Resp)(nil)).Elem(), 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc3/requests.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. 2 | // source: requests.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package svc3 is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | requests.proto 10 | 11 | It has these top-level messages: 12 | Req 13 | Resp 14 | */ 15 | package svc3 16 | 17 | import proto "github.com/gogo/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | import io "io" 22 | 23 | // Reference imports to suppress errors if they are not otherwise used. 24 | var _ = proto.Marshal 25 | var _ = fmt.Errorf 26 | var _ = math.Inf 27 | 28 | // This is a compile-time assertion to ensure that this generated file 29 | // is compatible with the proto package it is being compiled against. 30 | // A compilation error at this line likely means your copy of the 31 | // proto package needs to be updated. 32 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 33 | 34 | type Req struct { 35 | Param string `protobuf:"bytes,1,opt,name=param,proto3" json:"param,omitempty"` 36 | } 37 | 38 | func (m *Req) Reset() { *m = Req{} } 39 | func (m *Req) String() string { return proto.CompactTextString(m) } 40 | func (*Req) ProtoMessage() {} 41 | func (*Req) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{0} } 42 | 43 | func (m *Req) GetParam() string { 44 | if m != nil { 45 | return m.Param 46 | } 47 | return "" 48 | } 49 | 50 | type Resp struct { 51 | Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` 52 | } 53 | 54 | func (m *Resp) Reset() { *m = Resp{} } 55 | func (m *Resp) String() string { return proto.CompactTextString(m) } 56 | func (*Resp) ProtoMessage() {} 57 | func (*Resp) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{1} } 58 | 59 | func (m *Resp) GetValue() string { 60 | if m != nil { 61 | return m.Value 62 | } 63 | return "" 64 | } 65 | 66 | func init() { 67 | proto.RegisterType((*Req)(nil), "svc3.Req") 68 | proto.RegisterType((*Resp)(nil), "svc3.Resp") 69 | } 70 | func (m *Req) Marshal() (dAtA []byte, err error) { 71 | size := m.Size() 72 | dAtA = make([]byte, size) 73 | n, err := m.MarshalTo(dAtA) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return dAtA[:n], nil 78 | } 79 | 80 | func (m *Req) MarshalTo(dAtA []byte) (int, error) { 81 | var i int 82 | _ = i 83 | var l int 84 | _ = l 85 | if len(m.Param) > 0 { 86 | dAtA[i] = 0xa 87 | i++ 88 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Param))) 89 | i += copy(dAtA[i:], m.Param) 90 | } 91 | return i, nil 92 | } 93 | 94 | func (m *Resp) Marshal() (dAtA []byte, err error) { 95 | size := m.Size() 96 | dAtA = make([]byte, size) 97 | n, err := m.MarshalTo(dAtA) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return dAtA[:n], nil 102 | } 103 | 104 | func (m *Resp) MarshalTo(dAtA []byte) (int, error) { 105 | var i int 106 | _ = i 107 | var l int 108 | _ = l 109 | if len(m.Value) > 0 { 110 | dAtA[i] = 0xa 111 | i++ 112 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Value))) 113 | i += copy(dAtA[i:], m.Value) 114 | } 115 | return i, nil 116 | } 117 | 118 | func encodeFixed64Requests(dAtA []byte, offset int, v uint64) int { 119 | dAtA[offset] = uint8(v) 120 | dAtA[offset+1] = uint8(v >> 8) 121 | dAtA[offset+2] = uint8(v >> 16) 122 | dAtA[offset+3] = uint8(v >> 24) 123 | dAtA[offset+4] = uint8(v >> 32) 124 | dAtA[offset+5] = uint8(v >> 40) 125 | dAtA[offset+6] = uint8(v >> 48) 126 | dAtA[offset+7] = uint8(v >> 56) 127 | return offset + 8 128 | } 129 | func encodeFixed32Requests(dAtA []byte, offset int, v uint32) int { 130 | dAtA[offset] = uint8(v) 131 | dAtA[offset+1] = uint8(v >> 8) 132 | dAtA[offset+2] = uint8(v >> 16) 133 | dAtA[offset+3] = uint8(v >> 24) 134 | return offset + 4 135 | } 136 | func encodeVarintRequests(dAtA []byte, offset int, v uint64) int { 137 | for v >= 1<<7 { 138 | dAtA[offset] = uint8(v&0x7f | 0x80) 139 | v >>= 7 140 | offset++ 141 | } 142 | dAtA[offset] = uint8(v) 143 | return offset + 1 144 | } 145 | func (m *Req) Size() (n int) { 146 | var l int 147 | _ = l 148 | l = len(m.Param) 149 | if l > 0 { 150 | n += 1 + l + sovRequests(uint64(l)) 151 | } 152 | return n 153 | } 154 | 155 | func (m *Resp) Size() (n int) { 156 | var l int 157 | _ = l 158 | l = len(m.Value) 159 | if l > 0 { 160 | n += 1 + l + sovRequests(uint64(l)) 161 | } 162 | return n 163 | } 164 | 165 | func sovRequests(x uint64) (n int) { 166 | for { 167 | n++ 168 | x >>= 7 169 | if x == 0 { 170 | break 171 | } 172 | } 173 | return n 174 | } 175 | func sozRequests(x uint64) (n int) { 176 | return sovRequests(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 177 | } 178 | func (m *Req) Unmarshal(dAtA []byte) error { 179 | l := len(dAtA) 180 | iNdEx := 0 181 | for iNdEx < l { 182 | preIndex := iNdEx 183 | var wire uint64 184 | for shift := uint(0); ; shift += 7 { 185 | if shift >= 64 { 186 | return ErrIntOverflowRequests 187 | } 188 | if iNdEx >= l { 189 | return io.ErrUnexpectedEOF 190 | } 191 | b := dAtA[iNdEx] 192 | iNdEx++ 193 | wire |= (uint64(b) & 0x7F) << shift 194 | if b < 0x80 { 195 | break 196 | } 197 | } 198 | fieldNum := int32(wire >> 3) 199 | wireType := int(wire & 0x7) 200 | if wireType == 4 { 201 | return fmt.Errorf("proto: Req: wiretype end group for non-group") 202 | } 203 | if fieldNum <= 0 { 204 | return fmt.Errorf("proto: Req: illegal tag %d (wire type %d)", fieldNum, wire) 205 | } 206 | switch fieldNum { 207 | case 1: 208 | if wireType != 2 { 209 | return fmt.Errorf("proto: wrong wireType = %d for field Param", wireType) 210 | } 211 | var stringLen uint64 212 | for shift := uint(0); ; shift += 7 { 213 | if shift >= 64 { 214 | return ErrIntOverflowRequests 215 | } 216 | if iNdEx >= l { 217 | return io.ErrUnexpectedEOF 218 | } 219 | b := dAtA[iNdEx] 220 | iNdEx++ 221 | stringLen |= (uint64(b) & 0x7F) << shift 222 | if b < 0x80 { 223 | break 224 | } 225 | } 226 | intStringLen := int(stringLen) 227 | if intStringLen < 0 { 228 | return ErrInvalidLengthRequests 229 | } 230 | postIndex := iNdEx + intStringLen 231 | if postIndex > l { 232 | return io.ErrUnexpectedEOF 233 | } 234 | m.Param = string(dAtA[iNdEx:postIndex]) 235 | iNdEx = postIndex 236 | default: 237 | iNdEx = preIndex 238 | skippy, err := skipRequests(dAtA[iNdEx:]) 239 | if err != nil { 240 | return err 241 | } 242 | if skippy < 0 { 243 | return ErrInvalidLengthRequests 244 | } 245 | if (iNdEx + skippy) > l { 246 | return io.ErrUnexpectedEOF 247 | } 248 | iNdEx += skippy 249 | } 250 | } 251 | 252 | if iNdEx > l { 253 | return io.ErrUnexpectedEOF 254 | } 255 | return nil 256 | } 257 | func (m *Resp) Unmarshal(dAtA []byte) error { 258 | l := len(dAtA) 259 | iNdEx := 0 260 | for iNdEx < l { 261 | preIndex := iNdEx 262 | var wire uint64 263 | for shift := uint(0); ; shift += 7 { 264 | if shift >= 64 { 265 | return ErrIntOverflowRequests 266 | } 267 | if iNdEx >= l { 268 | return io.ErrUnexpectedEOF 269 | } 270 | b := dAtA[iNdEx] 271 | iNdEx++ 272 | wire |= (uint64(b) & 0x7F) << shift 273 | if b < 0x80 { 274 | break 275 | } 276 | } 277 | fieldNum := int32(wire >> 3) 278 | wireType := int(wire & 0x7) 279 | if wireType == 4 { 280 | return fmt.Errorf("proto: Resp: wiretype end group for non-group") 281 | } 282 | if fieldNum <= 0 { 283 | return fmt.Errorf("proto: Resp: illegal tag %d (wire type %d)", fieldNum, wire) 284 | } 285 | switch fieldNum { 286 | case 1: 287 | if wireType != 2 { 288 | return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) 289 | } 290 | var stringLen uint64 291 | for shift := uint(0); ; shift += 7 { 292 | if shift >= 64 { 293 | return ErrIntOverflowRequests 294 | } 295 | if iNdEx >= l { 296 | return io.ErrUnexpectedEOF 297 | } 298 | b := dAtA[iNdEx] 299 | iNdEx++ 300 | stringLen |= (uint64(b) & 0x7F) << shift 301 | if b < 0x80 { 302 | break 303 | } 304 | } 305 | intStringLen := int(stringLen) 306 | if intStringLen < 0 { 307 | return ErrInvalidLengthRequests 308 | } 309 | postIndex := iNdEx + intStringLen 310 | if postIndex > l { 311 | return io.ErrUnexpectedEOF 312 | } 313 | m.Value = string(dAtA[iNdEx:postIndex]) 314 | iNdEx = postIndex 315 | default: 316 | iNdEx = preIndex 317 | skippy, err := skipRequests(dAtA[iNdEx:]) 318 | if err != nil { 319 | return err 320 | } 321 | if skippy < 0 { 322 | return ErrInvalidLengthRequests 323 | } 324 | if (iNdEx + skippy) > l { 325 | return io.ErrUnexpectedEOF 326 | } 327 | iNdEx += skippy 328 | } 329 | } 330 | 331 | if iNdEx > l { 332 | return io.ErrUnexpectedEOF 333 | } 334 | return nil 335 | } 336 | func skipRequests(dAtA []byte) (n int, err error) { 337 | l := len(dAtA) 338 | iNdEx := 0 339 | for iNdEx < l { 340 | var wire uint64 341 | for shift := uint(0); ; shift += 7 { 342 | if shift >= 64 { 343 | return 0, ErrIntOverflowRequests 344 | } 345 | if iNdEx >= l { 346 | return 0, io.ErrUnexpectedEOF 347 | } 348 | b := dAtA[iNdEx] 349 | iNdEx++ 350 | wire |= (uint64(b) & 0x7F) << shift 351 | if b < 0x80 { 352 | break 353 | } 354 | } 355 | wireType := int(wire & 0x7) 356 | switch wireType { 357 | case 0: 358 | for shift := uint(0); ; shift += 7 { 359 | if shift >= 64 { 360 | return 0, ErrIntOverflowRequests 361 | } 362 | if iNdEx >= l { 363 | return 0, io.ErrUnexpectedEOF 364 | } 365 | iNdEx++ 366 | if dAtA[iNdEx-1] < 0x80 { 367 | break 368 | } 369 | } 370 | return iNdEx, nil 371 | case 1: 372 | iNdEx += 8 373 | return iNdEx, nil 374 | case 2: 375 | var length int 376 | for shift := uint(0); ; shift += 7 { 377 | if shift >= 64 { 378 | return 0, ErrIntOverflowRequests 379 | } 380 | if iNdEx >= l { 381 | return 0, io.ErrUnexpectedEOF 382 | } 383 | b := dAtA[iNdEx] 384 | iNdEx++ 385 | length |= (int(b) & 0x7F) << shift 386 | if b < 0x80 { 387 | break 388 | } 389 | } 390 | iNdEx += length 391 | if length < 0 { 392 | return 0, ErrInvalidLengthRequests 393 | } 394 | return iNdEx, nil 395 | case 3: 396 | for { 397 | var innerWire uint64 398 | var start int = iNdEx 399 | for shift := uint(0); ; shift += 7 { 400 | if shift >= 64 { 401 | return 0, ErrIntOverflowRequests 402 | } 403 | if iNdEx >= l { 404 | return 0, io.ErrUnexpectedEOF 405 | } 406 | b := dAtA[iNdEx] 407 | iNdEx++ 408 | innerWire |= (uint64(b) & 0x7F) << shift 409 | if b < 0x80 { 410 | break 411 | } 412 | } 413 | innerWireType := int(innerWire & 0x7) 414 | if innerWireType == 4 { 415 | break 416 | } 417 | next, err := skipRequests(dAtA[start:]) 418 | if err != nil { 419 | return 0, err 420 | } 421 | iNdEx = start + next 422 | } 423 | return iNdEx, nil 424 | case 4: 425 | return iNdEx, nil 426 | case 5: 427 | iNdEx += 4 428 | return iNdEx, nil 429 | default: 430 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 431 | } 432 | } 433 | panic("unreachable") 434 | } 435 | 436 | var ( 437 | ErrInvalidLengthRequests = fmt.Errorf("proto: negative length found during unmarshaling") 438 | ErrIntOverflowRequests = fmt.Errorf("proto: integer overflow") 439 | ) 440 | 441 | func init() { proto.RegisterFile("requests.proto", fileDescriptorRequests) } 442 | 443 | var fileDescriptorRequests = []byte{ 444 | // 113 bytes of a gzipped FileDescriptorProto 445 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2b, 0x4a, 0x2d, 0x2c, 446 | 0x4d, 0x2d, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x29, 0x2e, 0x4b, 0x36, 447 | 0x56, 0x92, 0xe6, 0x62, 0x0e, 0x4a, 0x2d, 0x14, 0x12, 0xe1, 0x62, 0x2d, 0x48, 0x2c, 0x4a, 0xcc, 448 | 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x94, 0x64, 0xb8, 0x58, 0x82, 0x52, 0x8b, 449 | 0x0b, 0x40, 0xb2, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x30, 0x59, 0x30, 0xc7, 0x49, 0xe0, 0xc4, 0x23, 450 | 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0x21, 0x89, 451 | 0x0d, 0x6c, 0xb2, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xf9, 0x57, 0x40, 0xa2, 0x6b, 0x00, 0x00, 452 | 0x00, 453 | } 454 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc4/http_transport_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | DO NOT EDIT! 3 | This file has been generated from JSON by gen_http_transport_config. 4 | */ 5 | package svc4 6 | 7 | import ( 8 | "reflect" 9 | 10 | "github.com/pasztorpisti/nano/addons/transport/http/config" 11 | ) 12 | 13 | var HTTPTransportConfig = &config.ServiceConfig{ 14 | ServiceName: "svc4", 15 | Endpoints: []*config.EndpointConfig{ 16 | { 17 | Method: "POST", 18 | Path: "/", 19 | HasReqContent: true, 20 | ReqType: reflect.TypeOf((*Req)(nil)).Elem(), 21 | RespType: reflect.TypeOf((*Resp)(nil)).Elem(), 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /examples/example1/api_go/svc4/requests.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. 2 | // source: requests.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package svc4 is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | requests.proto 10 | 11 | It has these top-level messages: 12 | Req 13 | Resp 14 | */ 15 | package svc4 16 | 17 | import proto "github.com/gogo/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | import io "io" 22 | 23 | // Reference imports to suppress errors if they are not otherwise used. 24 | var _ = proto.Marshal 25 | var _ = fmt.Errorf 26 | var _ = math.Inf 27 | 28 | // This is a compile-time assertion to ensure that this generated file 29 | // is compatible with the proto package it is being compiled against. 30 | // A compilation error at this line likely means your copy of the 31 | // proto package needs to be updated. 32 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 33 | 34 | type Req struct { 35 | Param string `protobuf:"bytes,1,opt,name=param,proto3" json:"param,omitempty"` 36 | } 37 | 38 | func (m *Req) Reset() { *m = Req{} } 39 | func (m *Req) String() string { return proto.CompactTextString(m) } 40 | func (*Req) ProtoMessage() {} 41 | func (*Req) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{0} } 42 | 43 | func (m *Req) GetParam() string { 44 | if m != nil { 45 | return m.Param 46 | } 47 | return "" 48 | } 49 | 50 | type Resp struct { 51 | Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` 52 | } 53 | 54 | func (m *Resp) Reset() { *m = Resp{} } 55 | func (m *Resp) String() string { return proto.CompactTextString(m) } 56 | func (*Resp) ProtoMessage() {} 57 | func (*Resp) Descriptor() ([]byte, []int) { return fileDescriptorRequests, []int{1} } 58 | 59 | func (m *Resp) GetValue() string { 60 | if m != nil { 61 | return m.Value 62 | } 63 | return "" 64 | } 65 | 66 | func init() { 67 | proto.RegisterType((*Req)(nil), "svc4.Req") 68 | proto.RegisterType((*Resp)(nil), "svc4.Resp") 69 | } 70 | func (m *Req) Marshal() (dAtA []byte, err error) { 71 | size := m.Size() 72 | dAtA = make([]byte, size) 73 | n, err := m.MarshalTo(dAtA) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return dAtA[:n], nil 78 | } 79 | 80 | func (m *Req) MarshalTo(dAtA []byte) (int, error) { 81 | var i int 82 | _ = i 83 | var l int 84 | _ = l 85 | if len(m.Param) > 0 { 86 | dAtA[i] = 0xa 87 | i++ 88 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Param))) 89 | i += copy(dAtA[i:], m.Param) 90 | } 91 | return i, nil 92 | } 93 | 94 | func (m *Resp) Marshal() (dAtA []byte, err error) { 95 | size := m.Size() 96 | dAtA = make([]byte, size) 97 | n, err := m.MarshalTo(dAtA) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return dAtA[:n], nil 102 | } 103 | 104 | func (m *Resp) MarshalTo(dAtA []byte) (int, error) { 105 | var i int 106 | _ = i 107 | var l int 108 | _ = l 109 | if len(m.Value) > 0 { 110 | dAtA[i] = 0xa 111 | i++ 112 | i = encodeVarintRequests(dAtA, i, uint64(len(m.Value))) 113 | i += copy(dAtA[i:], m.Value) 114 | } 115 | return i, nil 116 | } 117 | 118 | func encodeFixed64Requests(dAtA []byte, offset int, v uint64) int { 119 | dAtA[offset] = uint8(v) 120 | dAtA[offset+1] = uint8(v >> 8) 121 | dAtA[offset+2] = uint8(v >> 16) 122 | dAtA[offset+3] = uint8(v >> 24) 123 | dAtA[offset+4] = uint8(v >> 32) 124 | dAtA[offset+5] = uint8(v >> 40) 125 | dAtA[offset+6] = uint8(v >> 48) 126 | dAtA[offset+7] = uint8(v >> 56) 127 | return offset + 8 128 | } 129 | func encodeFixed32Requests(dAtA []byte, offset int, v uint32) int { 130 | dAtA[offset] = uint8(v) 131 | dAtA[offset+1] = uint8(v >> 8) 132 | dAtA[offset+2] = uint8(v >> 16) 133 | dAtA[offset+3] = uint8(v >> 24) 134 | return offset + 4 135 | } 136 | func encodeVarintRequests(dAtA []byte, offset int, v uint64) int { 137 | for v >= 1<<7 { 138 | dAtA[offset] = uint8(v&0x7f | 0x80) 139 | v >>= 7 140 | offset++ 141 | } 142 | dAtA[offset] = uint8(v) 143 | return offset + 1 144 | } 145 | func (m *Req) Size() (n int) { 146 | var l int 147 | _ = l 148 | l = len(m.Param) 149 | if l > 0 { 150 | n += 1 + l + sovRequests(uint64(l)) 151 | } 152 | return n 153 | } 154 | 155 | func (m *Resp) Size() (n int) { 156 | var l int 157 | _ = l 158 | l = len(m.Value) 159 | if l > 0 { 160 | n += 1 + l + sovRequests(uint64(l)) 161 | } 162 | return n 163 | } 164 | 165 | func sovRequests(x uint64) (n int) { 166 | for { 167 | n++ 168 | x >>= 7 169 | if x == 0 { 170 | break 171 | } 172 | } 173 | return n 174 | } 175 | func sozRequests(x uint64) (n int) { 176 | return sovRequests(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 177 | } 178 | func (m *Req) Unmarshal(dAtA []byte) error { 179 | l := len(dAtA) 180 | iNdEx := 0 181 | for iNdEx < l { 182 | preIndex := iNdEx 183 | var wire uint64 184 | for shift := uint(0); ; shift += 7 { 185 | if shift >= 64 { 186 | return ErrIntOverflowRequests 187 | } 188 | if iNdEx >= l { 189 | return io.ErrUnexpectedEOF 190 | } 191 | b := dAtA[iNdEx] 192 | iNdEx++ 193 | wire |= (uint64(b) & 0x7F) << shift 194 | if b < 0x80 { 195 | break 196 | } 197 | } 198 | fieldNum := int32(wire >> 3) 199 | wireType := int(wire & 0x7) 200 | if wireType == 4 { 201 | return fmt.Errorf("proto: Req: wiretype end group for non-group") 202 | } 203 | if fieldNum <= 0 { 204 | return fmt.Errorf("proto: Req: illegal tag %d (wire type %d)", fieldNum, wire) 205 | } 206 | switch fieldNum { 207 | case 1: 208 | if wireType != 2 { 209 | return fmt.Errorf("proto: wrong wireType = %d for field Param", wireType) 210 | } 211 | var stringLen uint64 212 | for shift := uint(0); ; shift += 7 { 213 | if shift >= 64 { 214 | return ErrIntOverflowRequests 215 | } 216 | if iNdEx >= l { 217 | return io.ErrUnexpectedEOF 218 | } 219 | b := dAtA[iNdEx] 220 | iNdEx++ 221 | stringLen |= (uint64(b) & 0x7F) << shift 222 | if b < 0x80 { 223 | break 224 | } 225 | } 226 | intStringLen := int(stringLen) 227 | if intStringLen < 0 { 228 | return ErrInvalidLengthRequests 229 | } 230 | postIndex := iNdEx + intStringLen 231 | if postIndex > l { 232 | return io.ErrUnexpectedEOF 233 | } 234 | m.Param = string(dAtA[iNdEx:postIndex]) 235 | iNdEx = postIndex 236 | default: 237 | iNdEx = preIndex 238 | skippy, err := skipRequests(dAtA[iNdEx:]) 239 | if err != nil { 240 | return err 241 | } 242 | if skippy < 0 { 243 | return ErrInvalidLengthRequests 244 | } 245 | if (iNdEx + skippy) > l { 246 | return io.ErrUnexpectedEOF 247 | } 248 | iNdEx += skippy 249 | } 250 | } 251 | 252 | if iNdEx > l { 253 | return io.ErrUnexpectedEOF 254 | } 255 | return nil 256 | } 257 | func (m *Resp) Unmarshal(dAtA []byte) error { 258 | l := len(dAtA) 259 | iNdEx := 0 260 | for iNdEx < l { 261 | preIndex := iNdEx 262 | var wire uint64 263 | for shift := uint(0); ; shift += 7 { 264 | if shift >= 64 { 265 | return ErrIntOverflowRequests 266 | } 267 | if iNdEx >= l { 268 | return io.ErrUnexpectedEOF 269 | } 270 | b := dAtA[iNdEx] 271 | iNdEx++ 272 | wire |= (uint64(b) & 0x7F) << shift 273 | if b < 0x80 { 274 | break 275 | } 276 | } 277 | fieldNum := int32(wire >> 3) 278 | wireType := int(wire & 0x7) 279 | if wireType == 4 { 280 | return fmt.Errorf("proto: Resp: wiretype end group for non-group") 281 | } 282 | if fieldNum <= 0 { 283 | return fmt.Errorf("proto: Resp: illegal tag %d (wire type %d)", fieldNum, wire) 284 | } 285 | switch fieldNum { 286 | case 1: 287 | if wireType != 2 { 288 | return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) 289 | } 290 | var stringLen uint64 291 | for shift := uint(0); ; shift += 7 { 292 | if shift >= 64 { 293 | return ErrIntOverflowRequests 294 | } 295 | if iNdEx >= l { 296 | return io.ErrUnexpectedEOF 297 | } 298 | b := dAtA[iNdEx] 299 | iNdEx++ 300 | stringLen |= (uint64(b) & 0x7F) << shift 301 | if b < 0x80 { 302 | break 303 | } 304 | } 305 | intStringLen := int(stringLen) 306 | if intStringLen < 0 { 307 | return ErrInvalidLengthRequests 308 | } 309 | postIndex := iNdEx + intStringLen 310 | if postIndex > l { 311 | return io.ErrUnexpectedEOF 312 | } 313 | m.Value = string(dAtA[iNdEx:postIndex]) 314 | iNdEx = postIndex 315 | default: 316 | iNdEx = preIndex 317 | skippy, err := skipRequests(dAtA[iNdEx:]) 318 | if err != nil { 319 | return err 320 | } 321 | if skippy < 0 { 322 | return ErrInvalidLengthRequests 323 | } 324 | if (iNdEx + skippy) > l { 325 | return io.ErrUnexpectedEOF 326 | } 327 | iNdEx += skippy 328 | } 329 | } 330 | 331 | if iNdEx > l { 332 | return io.ErrUnexpectedEOF 333 | } 334 | return nil 335 | } 336 | func skipRequests(dAtA []byte) (n int, err error) { 337 | l := len(dAtA) 338 | iNdEx := 0 339 | for iNdEx < l { 340 | var wire uint64 341 | for shift := uint(0); ; shift += 7 { 342 | if shift >= 64 { 343 | return 0, ErrIntOverflowRequests 344 | } 345 | if iNdEx >= l { 346 | return 0, io.ErrUnexpectedEOF 347 | } 348 | b := dAtA[iNdEx] 349 | iNdEx++ 350 | wire |= (uint64(b) & 0x7F) << shift 351 | if b < 0x80 { 352 | break 353 | } 354 | } 355 | wireType := int(wire & 0x7) 356 | switch wireType { 357 | case 0: 358 | for shift := uint(0); ; shift += 7 { 359 | if shift >= 64 { 360 | return 0, ErrIntOverflowRequests 361 | } 362 | if iNdEx >= l { 363 | return 0, io.ErrUnexpectedEOF 364 | } 365 | iNdEx++ 366 | if dAtA[iNdEx-1] < 0x80 { 367 | break 368 | } 369 | } 370 | return iNdEx, nil 371 | case 1: 372 | iNdEx += 8 373 | return iNdEx, nil 374 | case 2: 375 | var length int 376 | for shift := uint(0); ; shift += 7 { 377 | if shift >= 64 { 378 | return 0, ErrIntOverflowRequests 379 | } 380 | if iNdEx >= l { 381 | return 0, io.ErrUnexpectedEOF 382 | } 383 | b := dAtA[iNdEx] 384 | iNdEx++ 385 | length |= (int(b) & 0x7F) << shift 386 | if b < 0x80 { 387 | break 388 | } 389 | } 390 | iNdEx += length 391 | if length < 0 { 392 | return 0, ErrInvalidLengthRequests 393 | } 394 | return iNdEx, nil 395 | case 3: 396 | for { 397 | var innerWire uint64 398 | var start int = iNdEx 399 | for shift := uint(0); ; shift += 7 { 400 | if shift >= 64 { 401 | return 0, ErrIntOverflowRequests 402 | } 403 | if iNdEx >= l { 404 | return 0, io.ErrUnexpectedEOF 405 | } 406 | b := dAtA[iNdEx] 407 | iNdEx++ 408 | innerWire |= (uint64(b) & 0x7F) << shift 409 | if b < 0x80 { 410 | break 411 | } 412 | } 413 | innerWireType := int(innerWire & 0x7) 414 | if innerWireType == 4 { 415 | break 416 | } 417 | next, err := skipRequests(dAtA[start:]) 418 | if err != nil { 419 | return 0, err 420 | } 421 | iNdEx = start + next 422 | } 423 | return iNdEx, nil 424 | case 4: 425 | return iNdEx, nil 426 | case 5: 427 | iNdEx += 4 428 | return iNdEx, nil 429 | default: 430 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 431 | } 432 | } 433 | panic("unreachable") 434 | } 435 | 436 | var ( 437 | ErrInvalidLengthRequests = fmt.Errorf("proto: negative length found during unmarshaling") 438 | ErrIntOverflowRequests = fmt.Errorf("proto: integer overflow") 439 | ) 440 | 441 | func init() { proto.RegisterFile("requests.proto", fileDescriptorRequests) } 442 | 443 | var fileDescriptorRequests = []byte{ 444 | // 113 bytes of a gzipped FileDescriptorProto 445 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2b, 0x4a, 0x2d, 0x2c, 446 | 0x4d, 0x2d, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x29, 0x2e, 0x4b, 0x36, 447 | 0x51, 0x92, 0xe6, 0x62, 0x0e, 0x4a, 0x2d, 0x14, 0x12, 0xe1, 0x62, 0x2d, 0x48, 0x2c, 0x4a, 0xcc, 448 | 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x94, 0x64, 0xb8, 0x58, 0x82, 0x52, 0x8b, 449 | 0x0b, 0x40, 0xb2, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x30, 0x59, 0x30, 0xc7, 0x49, 0xe0, 0xc4, 0x23, 450 | 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0x21, 0x89, 451 | 0x0d, 0x6c, 0xb2, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x6d, 0xc4, 0xae, 0xd1, 0x6b, 0x00, 0x00, 452 | 0x00, 453 | } 454 | -------------------------------------------------------------------------------- /examples/example1/config/README.md: -------------------------------------------------------------------------------- 1 | # config 2 | 3 | This package contains initialisation code shared by server and test executables 4 | to avoid duplication of the init code and make its maintenance simpler. 5 | -------------------------------------------------------------------------------- /examples/example1/config/client/config.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | net_http "net/http" 6 | 7 | "github.com/pasztorpisti/nano/addons/transport/http" 8 | "github.com/pasztorpisti/nano/addons/transport/http/serialization/json" 9 | "github.com/pasztorpisti/nano/examples/example1/config/common" 10 | ) 11 | 12 | func Init() { 13 | common.Init() 14 | 15 | http.DefaultClientOptions = &http.ClientOptions{ 16 | Client: net_http.DefaultClient, 17 | Discoverer: discoverer{}, 18 | Serializer: json.ClientSideSerializer, 19 | PrefixURLPath: true, 20 | } 21 | } 22 | 23 | type discoverer struct{} 24 | 25 | func (discoverer) Discover(name string) (string, error) { 26 | return fmt.Sprintf("%s:%d", name, common.Port), nil 27 | } 28 | -------------------------------------------------------------------------------- /examples/example1/config/common/config.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "flag" 4 | 5 | const ( 6 | // Port is the predefined fix port on which our servers will listen. 7 | // Since we run each service in its own container they have their own IP 8 | // address and a hostname that is the same as the service name. 9 | Port = 8000 10 | ) 11 | 12 | var alreadyInitialised = false 13 | 14 | // Init contains initialisation code that is shared by client and server config. 15 | func Init() { 16 | if alreadyInitialised { 17 | return 18 | } 19 | alreadyInitialised = true 20 | 21 | flag.Parse() 22 | } 23 | -------------------------------------------------------------------------------- /examples/example1/config/server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pasztorpisti/nano/addons/transport/http" 7 | "github.com/pasztorpisti/nano/addons/transport/http/serialization/json" 8 | "github.com/pasztorpisti/nano/examples/example1/config/common" 9 | ) 10 | 11 | func Init() { 12 | common.Init() 13 | 14 | http.DefaultListenerOptions = &http.ListenerOptions{ 15 | BindAddr: fmt.Sprintf(":%d", common.Port), 16 | Serializer: json.ServerSideSerializer, 17 | PrefixURLPath: true, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/example1/config/test/config.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/pasztorpisti/nano" 7 | "github.com/pasztorpisti/nano/addons/util" 8 | ) 9 | 10 | func Init() { 11 | flag.Parse() 12 | 13 | origNewClient := nano.NewClient 14 | nano.NewClient = func(svc nano.Service, ownerName string) nano.Client { 15 | return errorFilterClient{origNewClient(svc, ownerName)} 16 | } 17 | } 18 | 19 | // errorFilterClient implements nano.Client. When we execute business logic 20 | // tests all services (or a combination of services and mock services) reside 21 | // in the same test executable. For this reason services can return error 22 | // objects of any type without problems. However when we place the services into 23 | // separate server executables and put a transport layer between them the 24 | // transport layer will support only a few error types and can't return other 25 | // object types. This can cause different behavior between tests and server 26 | // executables that can lead to successful tests and code that can't detect 27 | // errors correctly when services are called through transport layer between 28 | // two servers. 29 | // 30 | // To avoid the previous scenario we hook the nano.NewClient in this test config 31 | // package and wrap the client returned by the original nano.NewClient function 32 | // into an instance of this client. This client makes sure that during tests 33 | // errors are returned the same way as our transport implementation returns them 34 | // through network. We use the github.com/pasztorpisti/nano/addons/transport/http 35 | // transport that supports only the NanoError error type and always returns nil 36 | // or NanoError. This client simulates this behavior. 37 | type errorFilterClient struct { 38 | client nano.Client 39 | } 40 | 41 | func (p errorFilterClient) Request(c *nano.Ctx, req interface{}) (interface{}, error) { 42 | resp, err := p.client.Request(c, req) 43 | if err != nil { 44 | return nil, util.ErrCode(nil, util.GetErrCode(err), err.Error()) 45 | } 46 | return resp, nil 47 | } 48 | -------------------------------------------------------------------------------- /examples/example1/servers/server1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/transport/http" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc2" 7 | "github.com/pasztorpisti/nano/examples/example1/config/server" 8 | svc_svc1 "github.com/pasztorpisti/nano/examples/example1/services/svc1" 9 | svc_svc2 "github.com/pasztorpisti/nano/examples/example1/services/svc2" 10 | svc_svc3 "github.com/pasztorpisti/nano/examples/example1/services/svc3" 11 | svc_svc4 "github.com/pasztorpisti/nano/examples/example1/services/svc4" 12 | ) 13 | 14 | func main() { 15 | server.Init() 16 | 17 | ss := nano.NewServiceSet( 18 | svc_svc1.New(), 19 | svc_svc2.New(), 20 | svc_svc3.New(), 21 | svc_svc4.New(), 22 | ) 23 | listener := http.NewListener(nil, svc2.HTTPTransportConfig) 24 | nano.RunServer(ss, listener) 25 | } 26 | -------------------------------------------------------------------------------- /examples/example1/servers/server2a/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/transport/http" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc2" 7 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc3" 8 | "github.com/pasztorpisti/nano/examples/example1/config/client" 9 | "github.com/pasztorpisti/nano/examples/example1/config/server" 10 | svc_svc1 "github.com/pasztorpisti/nano/examples/example1/services/svc1" 11 | svc_svc2 "github.com/pasztorpisti/nano/examples/example1/services/svc2" 12 | ) 13 | 14 | func main() { 15 | client.Init() 16 | server.Init() 17 | 18 | ss := nano.NewServiceSet( 19 | svc_svc1.New(), 20 | svc_svc2.New(), 21 | http.NewClient(nil, svc3.HTTPTransportConfig), 22 | ) 23 | listener := http.NewListener(nil, svc2.HTTPTransportConfig) 24 | nano.RunServer(ss, listener) 25 | } 26 | -------------------------------------------------------------------------------- /examples/example1/servers/server2b/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/transport/http" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc3" 7 | "github.com/pasztorpisti/nano/examples/example1/config/server" 8 | svc_svc3 "github.com/pasztorpisti/nano/examples/example1/services/svc3" 9 | svc_svc4 "github.com/pasztorpisti/nano/examples/example1/services/svc4" 10 | ) 11 | 12 | func main() { 13 | server.Init() 14 | 15 | ss := nano.NewServiceSet( 16 | svc_svc3.New(), 17 | svc_svc4.New(), 18 | ) 19 | listener := http.NewListener(nil, svc3.HTTPTransportConfig) 20 | nano.RunServer(ss, listener) 21 | } 22 | -------------------------------------------------------------------------------- /examples/example1/servers/server3a/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/transport/http" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc1" 7 | "github.com/pasztorpisti/nano/examples/example1/config/server" 8 | svc_svc1 "github.com/pasztorpisti/nano/examples/example1/services/svc1" 9 | ) 10 | 11 | func main() { 12 | server.Init() 13 | 14 | ss := nano.NewServiceSet( 15 | svc_svc1.New(), 16 | ) 17 | listener := http.NewListener(nil, svc1.HTTPTransportConfig) 18 | nano.RunServer(ss, listener) 19 | } 20 | -------------------------------------------------------------------------------- /examples/example1/servers/server3b/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/transport/http" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc1" 7 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc2" 8 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc3" 9 | "github.com/pasztorpisti/nano/examples/example1/config/client" 10 | "github.com/pasztorpisti/nano/examples/example1/config/server" 11 | svc_svc2 "github.com/pasztorpisti/nano/examples/example1/services/svc2" 12 | ) 13 | 14 | func main() { 15 | client.Init() 16 | server.Init() 17 | 18 | ss := nano.NewServiceSet( 19 | http.NewClient(nil, svc1.HTTPTransportConfig), 20 | svc_svc2.New(), 21 | http.NewClient(nil, svc3.HTTPTransportConfig), 22 | ) 23 | listener := http.NewListener(nil, svc2.HTTPTransportConfig) 24 | nano.RunServer(ss, listener) 25 | } 26 | -------------------------------------------------------------------------------- /examples/example1/servers/server3c/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/transport/http" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc3" 7 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc4" 8 | "github.com/pasztorpisti/nano/examples/example1/config/client" 9 | "github.com/pasztorpisti/nano/examples/example1/config/server" 10 | svc_svc3 "github.com/pasztorpisti/nano/examples/example1/services/svc3" 11 | ) 12 | 13 | func main() { 14 | client.Init() 15 | server.Init() 16 | 17 | ss := nano.NewServiceSet( 18 | svc_svc3.New(), 19 | http.NewClient(nil, svc4.HTTPTransportConfig), 20 | ) 21 | listener := http.NewListener(nil, svc3.HTTPTransportConfig) 22 | nano.RunServer(ss, listener) 23 | } 24 | -------------------------------------------------------------------------------- /examples/example1/servers/server3d/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/transport/http" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc4" 7 | "github.com/pasztorpisti/nano/examples/example1/config/server" 8 | svc_svc4 "github.com/pasztorpisti/nano/examples/example1/services/svc4" 9 | ) 10 | 11 | func main() { 12 | server.Init() 13 | 14 | ss := nano.NewServiceSet( 15 | svc_svc4.New(), 16 | ) 17 | listener := http.NewListener(nil, svc4.HTTPTransportConfig) 18 | nano.RunServer(ss, listener) 19 | } 20 | -------------------------------------------------------------------------------- /examples/example1/servers/test_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/pasztorpisti/nano" 10 | "github.com/pasztorpisti/nano/addons/discovery/static" 11 | "github.com/pasztorpisti/nano/addons/transport/http" 12 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc2" 13 | "github.com/pasztorpisti/nano/examples/example1/config/client" 14 | "github.com/pasztorpisti/nano/examples/example1/config/common" 15 | ) 16 | 17 | var addr = flag.String("addr", fmt.Sprintf("localhost:%d", common.Port), 18 | "addr:port of the server to connect to") 19 | var reqType = flag.String("req", "Req", 20 | "The type of request to perform. Must be 'Req' or 'GetReq'.") 21 | 22 | func main() { 23 | client.Init() 24 | http.DefaultClientOptions.Discoverer = static.Discoverer{"svc2": *addr} 25 | 26 | ss := nano.NewTestClientSet( 27 | http.NewClient(nil, svc2.HTTPTransportConfig), 28 | ) 29 | svc2Client := ss.LookupClient("svc2") 30 | 31 | var req interface{} 32 | switch strings.ToLower(*reqType) { 33 | case "req": 34 | req = &svc2.Req{Param: "param"} 35 | case "getreq": 36 | req = &svc2.GetReq{} 37 | default: 38 | fmt.Printf("Invalid request type: %q\n", *reqType) 39 | os.Exit(1) 40 | } 41 | fmt.Printf("req=%#v\n", req) 42 | 43 | resp, err := svc2Client.Request(nil, req) 44 | 45 | fmt.Printf("resp=%#v err=%v\n", resp, err) 46 | 47 | if err != nil { 48 | os.Exit(1) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/README.md: -------------------------------------------------------------------------------- 1 | # tests 2 | 3 | ## Intro 4 | 5 | This is a set of quick and dirty integration tests for the servers. 6 | 7 | These tests will compile the severs found in the `nano/examples/example1/servers` 8 | directory to linux platform and then run them in docker containers to be able to 9 | execute a very primitive integration test against them. 10 | 11 | ## Requirements 12 | 13 | I've successfully tried the scripts on OS X and Debian Linux. 14 | 15 | Running these tests requires only a docker installation on your machine. Make 16 | sure that the user that runs the test scripts has docker access (tip: make sure 17 | the user is added to the docker group). 18 | 19 | No golang installation is needed on your machine because the `go build` is 20 | executed inside a container with the correct go version and environment. For 21 | this reason it's enough to clone this repo somewhere on your machine and run 22 | one of the `test_server1.sh`, `test_server2.sh` and `test_server3.sh` scripts. 23 | 24 | A lot of people use docker-compose or a similar tool to do some of the things 25 | done by my scripts but I've deliberately chosen to depend only on docker for 26 | this overly simple example. 27 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/helpers/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Builds all server/test executables in a linux golang container if the 4 | # executables haven't yet been built. You need only docker installation and 5 | # access, no golang installation needed. 6 | # 7 | 8 | set -euo pipefail 9 | 10 | cd "$( dirname "$0" )" 11 | source env.sh 12 | 13 | function main() { 14 | build_all_if_needed 15 | } 16 | 17 | function build_all_if_needed() { 18 | local NAME 19 | for NAME in "${ALL[@]}"; do 20 | if [[ ! -x "${BIN_DIR}/${NAME}" ]]; then 21 | # if at least one executable is missing then we rebuild 22 | build_all 23 | return 24 | fi 25 | done 26 | echo "The executables have already been built." 27 | } 28 | 29 | function build_all() { 30 | echo "Building all executables for linux ..." 31 | 32 | local TERM_PARAM= 33 | if [[ -t 1 ]]; then 34 | # making ctrl-c work if STDOUT is a terminal 35 | TERM_PARAM="-it" 36 | fi 37 | docker run --rm "${TERM_PARAM}" \ 38 | -v "${NANO_DIR}:/go/src/${NANO_PKG}" \ 39 | -w "/go/src/${SERVERS_PKG}/tests" \ 40 | -e "HOST_UID=${UID}" \ 41 | "${GOLANG_BUILD_IMAGE}" \ 42 | ./helpers/build_helper.sh do_it 43 | } 44 | 45 | main 46 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/helpers/build_helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script will execute inside a golang builder container. 4 | # 5 | 6 | set -euo pipefail 7 | 8 | cd "$( dirname "$0" )" 9 | source env.sh 10 | 11 | # Since things inside the container execute as root we don't want to place 12 | # the root-owned files directly to the mounted volumes. First we compile the 13 | # root-owned executables into a temp dir and then we change their owner to 14 | # ${HOST_UID} and then move them to the mounted volume. 15 | TEMP_BIN="/temp_bin" 16 | 17 | function main() { 18 | if [[ $# -ne 1 || $1 != "do_it" ]]; then 19 | >&2 echo "Don't run this manually." 20 | exit 1 21 | fi 22 | 23 | mkdir -p "${BIN_DIR}" 24 | 25 | local NAME 26 | for NAME in "${ALL[@]}"; do 27 | echo "building ${NAME} ..." 28 | build "${NAME}" 29 | done 30 | 31 | # The HOST_UID env is passed into the container by a docker command. 32 | chown -R "${HOST_UID}:${HOST_UID}" "${TEMP_BIN}" 33 | rm -rf "${BIN_DIR}" 34 | cp -rp "${TEMP_BIN}" "${BIN_DIR}" 35 | } 36 | 37 | function build() { 38 | local NAME="$1" 39 | local PKG="${SERVERS_PKG}/${NAME}" 40 | go get -d -v "${PKG}" 41 | go build \ 42 | -o "${TEMP_BIN}/${NAME}" \ 43 | -ldflags "-linkmode external -extldflags -static" \ 44 | "${PKG}" 45 | } 46 | 47 | main "$@" 48 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/helpers/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IFS=$'\n' 4 | 5 | THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | TESTS_DIR="$( cd "${THIS_DIR}/.." && pwd )" 7 | BIN_DIR="${TESTS_DIR}/bin" 8 | 9 | NANO_PKG="github.com/pasztorpisti/nano" 10 | NANO_DIR="$( cd "${TESTS_DIR}/../../../.." && pwd )" 11 | 12 | SERVERS_DIR="$( cd "${TESTS_DIR}/.." && pwd )" 13 | SERVERS_PKG="${NANO_PKG}/examples/example1/servers" 14 | 15 | # The name of the docker network to create. We create a separate docker 16 | # network because the embedded docker DNS works only with custom networks. 17 | # The default docker bridge works in legacy mode without DNS. 18 | NETWORK="nano" 19 | 20 | # Docker image to use to run the static linked server executables. 21 | IMAGE_NAME="alpine:3.4" 22 | 23 | # Docker image to use to build the server executables. 24 | GOLANG_BUILD_IMAGE="golang:1.7.4" 25 | 26 | # Every docker container we create will have this prefix. This makes mass 27 | # deletion of our containers easier by filter for this. 28 | CONTAINER_NAME_PREFIX="nano_test_" 29 | 30 | # List of package names to build under the nano/examples/example1/servers directory. 31 | ALL=( 32 | server1 33 | server2a 34 | server2b 35 | server3a 36 | server3b 37 | server3c 38 | server3d 39 | test_client 40 | ) 41 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/helpers/shutdown_servers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # - removes all containers that contain "${CONTAINER_NAME_PREFIX}" in their names 4 | # - removes the "nano" docker network 5 | # 6 | 7 | set -euo pipefail 8 | 9 | cd "$( dirname "$0" )" 10 | source env.sh 11 | 12 | function main() { 13 | rm_containers 14 | rm_network 15 | } 16 | 17 | function rm_containers() { 18 | local CONTAINERS="$( docker ps -aqf name="${CONTAINER_NAME_PREFIX}" )" 19 | if [[ -n "${CONTAINERS[@]}" ]]; then 20 | docker rm -f ${CONTAINERS[@]} >/dev/null 21 | fi 22 | } 23 | 24 | function network_exists() { 25 | docker network inspect "${NETWORK}" >&/dev/null 26 | } 27 | 28 | function rm_network() { 29 | if network_exists; then 30 | docker network rm "${NETWORK}" >/dev/null 31 | fi 32 | } 33 | 34 | main 35 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/helpers/start_servers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # - builds all server executables if necessary 4 | # - creates a "nano" docker network 5 | # - starts the specified servers in their own containers with the 6 | # specified hostnames for the containers 7 | # - waits for each server to become ready (until their /health-check becomes responsive) 8 | # 9 | # Note: In a real life scenario you can use docker-compose to start up and tear 10 | # down several containers and setup network between them. 11 | # In these simple test scripts I decided to depend only on docker-engine. 12 | # 13 | 14 | set -euo pipefail 15 | 16 | cd "$( dirname "$0" )" 17 | source env.sh 18 | 19 | HELP_TEXT=" 20 | Usage: $0 mapping [mapping [mapping [...]]] 21 | 22 | A mapping has the following format: 23 | server_name[:network_alias[:network_alias[:...]]] 24 | " 25 | 26 | function main() { 27 | if [[ $# -eq 0 ]]; then 28 | >&2 echo "${HELP_TEXT}" 29 | exit 1 30 | fi 31 | 32 | # Removing any leftover containers from a previously failed execution. 33 | ./shutdown_servers.sh 34 | ./build_all.sh 35 | 36 | # Creating the "nano" docker network for the server containers. 37 | # The containers will be able to find each other using the embedded docker DNS. 38 | # A service name can be used as a hostname to resolve the IP address of the 39 | # server that contains the service. 40 | create_network 41 | 42 | docker pull "${IMAGE_NAME}" 43 | 44 | local MAPPING 45 | for MAPPING in "$@"; do 46 | start_server_container "${MAPPING}" 47 | done 48 | 49 | for MAPPING in "$@"; do 50 | wait_for_server_ready "${MAPPING}" 51 | done 52 | } 53 | 54 | function start_server_container() { 55 | local ORIG_IFS="${IFS}" 56 | local IFS=":" 57 | local MAPPING=( $1 ) 58 | IFS="${ORIG_IFS}" 59 | 60 | local NAME="${MAPPING[0]}" 61 | local CNAME="${CONTAINER_NAME_PREFIX}${NAME}" 62 | 63 | local DOCKER_PARAMS=( 64 | run -dit 65 | --net "${NETWORK}" 66 | -h "${NAME}" 67 | --name "${CNAME}" 68 | -v "${BIN_DIR}":/servers 69 | ) 70 | if [[ -t 1 ]]; then 71 | # making ctrl-c work if STDOUT is a terminal 72 | DOCKER_PARAMS+=( -it ) 73 | fi 74 | 75 | local ALIAS 76 | for ALIAS in "${MAPPING[@]:1}"; do 77 | DOCKER_PARAMS+=( --network-alias "${ALIAS}" ) 78 | done 79 | 80 | DOCKER_PARAMS+=( 81 | "${IMAGE_NAME}" 82 | "/servers/${NAME}" 83 | ) 84 | 85 | echo "Starting ${NAME} ..." 86 | docker "${DOCKER_PARAMS[@]}" >/dev/null 87 | } 88 | 89 | function wait_for_server_ready() { 90 | local ORIG_IFS="${IFS}" 91 | local IFS=":" 92 | local MAPPING=( $1 ) 93 | IFS="${ORIG_IFS}" 94 | 95 | local NAME="${MAPPING[0]}" 96 | local CNAME="${CONTAINER_NAME_PREFIX}${NAME}" 97 | 98 | echo -n "Waiting for ${NAME} to become ready " 99 | 100 | # Waiting until the /health-check endpoint of the server becomes responsive. 101 | local CMD="cd /root && wget -q http://localhost:8000/health-check >&/dev/null" 102 | local ATTEMPT 103 | for ATTEMPT in $( seq 5 ); do 104 | if docker exec "${CNAME}" /bin/sh -c "${CMD}"; then 105 | echo "OK" 106 | break 107 | fi 108 | if [[ "${ATTEMPT}" -eq 5 ]]; then 109 | echo "FAILED" 110 | return 1 111 | fi 112 | sleep 1 113 | echo -n "." 114 | done 115 | } 116 | 117 | function network_exists() { 118 | docker network inspect "${NETWORK}" >&/dev/null 119 | } 120 | 121 | function create_network() { 122 | if ! network_exists; then 123 | echo "Creating docker network ${NETWORK} ..." 124 | docker network create "${NETWORK}" >/dev/null 125 | fi 126 | } 127 | 128 | main "$@" 129 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/helpers/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | cd "$( dirname "$0" )" 5 | source env.sh 6 | 7 | HELP_TEXT=" 8 | Usage: $0 mapping [mapping [mapping [...]]] 9 | 10 | A mapping has the following format: 11 | server_name[:network_alias[:network_alias[:...]]] 12 | " 13 | 14 | function main() { 15 | if [[ $# -eq 0 ]]; then 16 | >&2 echo "${HELP_TEXT}" 17 | exit 1 18 | fi 19 | 20 | trap ./shutdown_servers.sh EXIT 21 | ./start_servers.sh "$@" 22 | run_test 23 | echo "SUCCESS." 24 | } 25 | 26 | function run_test() { 27 | echo "Running test against svc2 ..." 28 | local TERM_PARAM= 29 | if [[ -t 1 ]]; then 30 | # making ctrl-c work if STDOUT is a terminal 31 | TERM_PARAM="-it" 32 | fi 33 | 34 | # This is a very primitive test: we simply check whether two types of 35 | # requests succeed without checking the actual response values. 36 | # A real world test should perform a lot of tests and should check the 37 | # response values. 38 | docker run --rm "${TERM_PARAM}" \ 39 | -v "${BIN_DIR}:/servers" \ 40 | --net "${NETWORK}" \ 41 | -w /servers \ 42 | "${IMAGE_NAME}" \ 43 | /bin/sh -c "/servers/test_client -addr svc2:8000 && /servers/test_client -addr svc2:8000 -req getreq" 44 | } 45 | 46 | main "$@" 47 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/test_server1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | cd "$( dirname "$0" )" 4 | helpers/test.sh server1:svc2 5 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/test_server2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | cd "$( dirname "$0" )" 4 | helpers/test.sh server2a:svc2 server2b:svc3 5 | -------------------------------------------------------------------------------- /examples/example1/servers/tests/test_server3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | cd "$( dirname "$0" )" 4 | helpers/test.sh server3a:svc1 server3b:svc2 server3c:svc3 server3d:svc4 5 | -------------------------------------------------------------------------------- /examples/example1/services/svc1/svc.go: -------------------------------------------------------------------------------- 1 | package svc1 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc1" 6 | ) 7 | 8 | func New() nano.Service { 9 | return &svc{} 10 | } 11 | 12 | type svc struct{} 13 | 14 | func (*svc) Name() string { 15 | return "svc1" 16 | } 17 | 18 | func (*svc) Handle(c *nano.Ctx, req interface{}) (interface{}, error) { 19 | switch r := req.(type) { 20 | case *svc1.Req: 21 | return &svc1.Resp{ 22 | Value: "svc1_" + r.Param, 23 | }, nil 24 | default: 25 | return nil, nano.BadReqTypeError 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/example1/services/svc2/svc.go: -------------------------------------------------------------------------------- 1 | package svc2 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/util" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc1" 7 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc2" 8 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc3" 9 | ) 10 | 11 | func New() nano.Service { 12 | return &svc{} 13 | } 14 | 15 | type svc struct { 16 | svc1 nano.Client 17 | svc3 nano.Client 18 | } 19 | 20 | func (*svc) Name() string { 21 | return "svc2" 22 | } 23 | 24 | func (p *svc) Init(cs nano.ClientSet) error { 25 | p.svc1 = cs.LookupClient("svc1") 26 | p.svc3 = cs.LookupClient("svc3") 27 | return nil 28 | } 29 | 30 | func (p *svc) Handle(c *nano.Ctx, req interface{}) (interface{}, error) { 31 | switch r := req.(type) { 32 | case *svc2.Req: 33 | return p.handleReq(c, r) 34 | case *svc2.GetReq: 35 | return p.handleGetReq(c) 36 | default: 37 | return nil, nano.BadReqTypeError 38 | } 39 | } 40 | 41 | func (p *svc) handleReq(c *nano.Ctx, r *svc2.Req) (interface{}, error) { 42 | resp, err := p.svc1.Request(c, &svc1.Req{Param: r.Param}) 43 | if err != nil { 44 | return nil, util.Err(err, "svc1 failure") 45 | } 46 | return &svc2.Resp{ 47 | Value: "svc2_" + resp.(*svc1.Resp).Value, 48 | }, nil 49 | } 50 | 51 | func (p *svc) handleGetReq(c *nano.Ctx) (interface{}, error) { 52 | resp, err := p.svc3.Request(c, &svc3.Req{Param: "getparam"}) 53 | if err != nil { 54 | return nil, util.Err(err, "svc3 failure") 55 | } 56 | return &svc2.GetResp{ 57 | Value: "svc2_" + resp.(*svc3.Resp).Value, 58 | }, nil 59 | } 60 | -------------------------------------------------------------------------------- /examples/example1/services/svc2/tests/test1/svc_test.go: -------------------------------------------------------------------------------- 1 | package test1 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/pasztorpisti/nano" 8 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc2" 9 | "github.com/pasztorpisti/nano/examples/example1/config/test" 10 | svc_svc1 "github.com/pasztorpisti/nano/examples/example1/services/svc1" 11 | svc_svc2 "github.com/pasztorpisti/nano/examples/example1/services/svc2" 12 | svc_svc3 "github.com/pasztorpisti/nano/examples/example1/services/svc3" 13 | svc_svc4 "github.com/pasztorpisti/nano/examples/example1/services/svc4" 14 | ) 15 | 16 | var svc2Client nano.Client 17 | 18 | func TestMain(m *testing.M) { 19 | test.Init() 20 | 21 | cs := nano.NewTestClientSet( 22 | svc_svc1.New(), 23 | svc_svc2.New(), 24 | svc_svc3.New(), 25 | svc_svc4.New(), 26 | ) 27 | svc2Client = cs.LookupClient("svc2") 28 | 29 | os.Exit(m.Run()) 30 | } 31 | 32 | func TestReq(t *testing.T) { 33 | respObj, err := svc2Client.Request(nil, &svc2.Req{Param: "testval"}) 34 | if err != nil { 35 | t.Errorf("unexpected error: %v", err) 36 | t.FailNow() 37 | } 38 | 39 | resp, ok := respObj.(*svc2.Resp) 40 | if !ok { 41 | t.Errorf("response type == %T, want %T", resp, &svc2.Resp{}) 42 | t.FailNow() 43 | } 44 | 45 | want := "svc2_svc1_testval" 46 | if resp.Value != want { 47 | t.Errorf("response value == %q, want %q", resp.Value, want) 48 | } 49 | } 50 | 51 | func TestGetReq(t *testing.T) { 52 | respObj, err := svc2Client.Request(nil, &svc2.GetReq{}) 53 | if err != nil { 54 | t.Errorf("unexpected error: %v", err) 55 | t.FailNow() 56 | } 57 | 58 | resp, ok := respObj.(*svc2.GetResp) 59 | if !ok { 60 | t.Errorf("response type == %T, want %T", resp, &svc2.GetResp{}) 61 | t.FailNow() 62 | } 63 | 64 | want := "svc2_svc3_svc4_getparam" 65 | if resp.Value != want { 66 | t.Errorf("response value == %q, want %q", resp.Value, want) 67 | } 68 | } 69 | 70 | func TestBadRequest(t *testing.T) { 71 | _, err := svc2Client.Request(nil, nil) 72 | if err.Error() != nano.BadReqTypeError.Error() { 73 | t.Errorf("err == %v, want %v", err, nano.BadReqTypeError) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/example1/services/svc2/tests/test2/svc_test.go: -------------------------------------------------------------------------------- 1 | package test2 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/pasztorpisti/nano" 8 | "github.com/pasztorpisti/nano/addons/util" 9 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc1" 10 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc2" 11 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc3" 12 | "github.com/pasztorpisti/nano/examples/example1/config/test" 13 | svc_svc2 "github.com/pasztorpisti/nano/examples/example1/services/svc2" 14 | ) 15 | 16 | var svc2Client nano.Client 17 | 18 | func TestMain(m *testing.M) { 19 | test.Init() 20 | 21 | svc1Mock := util.NewService("svc1", func(c *nano.Ctx, req interface{}) (interface{}, error) { 22 | return &svc1.Resp{Value: "svc1_" + req.(*svc1.Req).Param}, nil 23 | }) 24 | 25 | // The svc1 and svc3 mocks demonstrate two different ways of creating mocks. 26 | cs := nano.NewTestClientSet( 27 | svc1Mock, 28 | &svc3Mock{}, 29 | svc_svc2.New(), 30 | ) 31 | svc2Client = cs.LookupClient("svc2") 32 | 33 | os.Exit(m.Run()) 34 | } 35 | 36 | type svc3Mock struct{} 37 | 38 | func (*svc3Mock) Name() string { 39 | return "svc3" 40 | } 41 | 42 | func (p *svc3Mock) Handle(c *nano.Ctx, req interface{}) (interface{}, error) { 43 | switch r := req.(type) { 44 | case *svc3.Req: 45 | return &svc3.Resp{Value: "svc3_svc4_" + r.Param}, nil 46 | default: 47 | return nil, nano.BadReqTypeError 48 | } 49 | } 50 | 51 | func TestReq(t *testing.T) { 52 | respObj, err := svc2Client.Request(nil, &svc2.Req{Param: "testval"}) 53 | if err != nil { 54 | t.Errorf("unexpected error: %v", err) 55 | t.FailNow() 56 | } 57 | 58 | resp, ok := respObj.(*svc2.Resp) 59 | if !ok { 60 | t.Errorf("response type == %T, want %T", resp, &svc2.Resp{}) 61 | t.FailNow() 62 | } 63 | 64 | want := "svc2_svc1_testval" 65 | if resp.Value != want { 66 | t.Errorf("response value == %q, want %q", resp.Value, want) 67 | } 68 | } 69 | 70 | func TestGetReq(t *testing.T) { 71 | respObj, err := svc2Client.Request(nil, &svc2.GetReq{}) 72 | if err != nil { 73 | t.Errorf("unexpected error: %v", err) 74 | t.FailNow() 75 | } 76 | 77 | resp, ok := respObj.(*svc2.GetResp) 78 | if !ok { 79 | t.Errorf("response type == %T, want %T", resp, &svc2.GetResp{}) 80 | t.FailNow() 81 | } 82 | 83 | want := "svc2_svc3_svc4_getparam" 84 | if resp.Value != want { 85 | t.Errorf("response value == %q, want %q", resp.Value, want) 86 | } 87 | } 88 | 89 | func TestBadRequest(t *testing.T) { 90 | _, err := svc2Client.Request(nil, nil) 91 | if err.Error() != nano.BadReqTypeError.Error() { 92 | t.Errorf("err == %v, want %v", err, nano.BadReqTypeError) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/example1/services/svc3/svc.go: -------------------------------------------------------------------------------- 1 | package svc3 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/addons/util" 6 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc3" 7 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc4" 8 | ) 9 | 10 | func New() nano.Service { 11 | return &svc{} 12 | } 13 | 14 | type svc struct { 15 | svc4 nano.Client 16 | } 17 | 18 | func (*svc) Name() string { 19 | return "svc3" 20 | } 21 | 22 | func (p *svc) Init(cs nano.ClientSet) error { 23 | p.svc4 = cs.LookupClient("svc4") 24 | return nil 25 | } 26 | 27 | func (p *svc) Handle(c *nano.Ctx, req interface{}) (interface{}, error) { 28 | switch r := req.(type) { 29 | case *svc3.Req: 30 | return p.handleReq(c, r) 31 | default: 32 | return nil, nano.BadReqTypeError 33 | } 34 | } 35 | 36 | func (p *svc) handleReq(c *nano.Ctx, r *svc3.Req) (interface{}, error) { 37 | resp, err := p.svc4.Request(c, &svc4.Req{Param: r.Param}) 38 | if err != nil { 39 | return nil, util.Err(err, "svc4 failure") 40 | } 41 | return &svc3.Resp{ 42 | Value: "svc3_" + resp.(*svc4.Resp).Value, 43 | }, nil 44 | } 45 | -------------------------------------------------------------------------------- /examples/example1/services/svc4/svc.go: -------------------------------------------------------------------------------- 1 | package svc4 2 | 3 | import ( 4 | "github.com/pasztorpisti/nano" 5 | "github.com/pasztorpisti/nano/examples/example1/api_go/svc4" 6 | ) 7 | 8 | func New() nano.Service { 9 | return &svc{} 10 | } 11 | 12 | type svc struct{} 13 | 14 | func (*svc) Name() string { 15 | return "svc4" 16 | } 17 | 18 | func (*svc) Handle(c *nano.Ctx, req interface{}) (interface{}, error) { 19 | switch r := req.(type) { 20 | case *svc4.Req: 21 | return &svc4.Resp{ 22 | Value: "svc4_" + r.Param, 23 | }, nil 24 | default: 25 | return nil, nano.BadReqTypeError 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /nano.go: -------------------------------------------------------------------------------- 1 | package nano 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "os" 10 | "sync" 11 | ) 12 | 13 | // BadReqTypeError should be returned by the Service.Handle method when it 14 | // receives a req object of type it can't handle. 15 | var BadReqTypeError = errors.New("bad request type") 16 | 17 | // RunServer takes a set of initialised services and a list of listeners and 18 | // initialises the listeners and then listens with them. Blocks and returns only 19 | // when all listeners returned. 20 | // 21 | // It panics if the Init of a listener returns an error. Terminates the server 22 | // process if the Listen method of a listener returns with an error. 23 | var RunServer = func(ss ServiceSet, listeners ...Listener) { 24 | runServer(func(l Listener, err error) { 25 | log.Println("Listener failure :: " + err.Error()) 26 | os.Exit(1) 27 | }, ss, listeners...) 28 | } 29 | 30 | var runServer = func(onError func(Listener, error), ss ServiceSet, listeners ...Listener) { 31 | for _, listener := range listeners { 32 | err := listener.Init(ss) 33 | if err != nil { 34 | panic("Error initialising listener :: " + err.Error()) 35 | } 36 | } 37 | 38 | var wg sync.WaitGroup 39 | for _, listener := range listeners { 40 | wg.Add(1) 41 | listener := listener 42 | go func() { 43 | defer wg.Done() 44 | err := listener.Listen() 45 | if err != nil { 46 | onError(listener, err) 47 | } 48 | }() 49 | } 50 | wg.Wait() 51 | } 52 | 53 | // NewServiceSet creates a new ServiceSet object from the given services. 54 | // The creation of the ServiceSet involves initialising the services before this 55 | // function returns. The initialisation of the services includes the resolution 56 | // of dependencies between each other by obtaining Client interfaces to each 57 | // other in their Init methods. 58 | var NewServiceSet = func(services ...Service) ServiceSet { 59 | ss := make(serviceSet, len(services)) 60 | for _, svc := range services { 61 | ss[svc.Name()] = svc 62 | } 63 | 64 | for _, svc := range services { 65 | if serviceInit, ok := svc.(ServiceInit); ok { 66 | err := serviceInit.Init(NewClientSet(ss, svc.Name())) 67 | if err != nil { 68 | panic(fmt.Sprintf("error initialising service %q :: %v", svc.Name(), err)) 69 | } 70 | } 71 | } 72 | 73 | for _, svc := range services { 74 | if serviceInitFinished, ok := svc.(ServiceInitFinished); ok { 75 | err := serviceInitFinished.InitFinished() 76 | if err != nil { 77 | panic(fmt.Sprintf("InitFinished error in service %q :: %v", svc.Name(), err)) 78 | } 79 | } 80 | } 81 | 82 | return ss 83 | } 84 | 85 | // NewTestClientSet is a convenience helper for tests to wrap a set of services 86 | // into a ServiceSet and then into a ClientSet in one step. 87 | var NewTestClientSet = func(services ...Service) ClientSet { 88 | return NewClientSet(NewServiceSet(services...), "test") 89 | } 90 | 91 | // NewClientSet creates a ClientSet object from the given ServiceSet for the 92 | // owner identified by ownerName. The ownerName can be anything but it should 93 | // be the name of the service if the ClientSet is created for a service. 94 | // 95 | // The returned ClientSet belongs to the owner specified by ownerName and 96 | // requests made through the Client objects returned by this ClientSet will send 97 | // the ownerName to the called services which can inspect ownerName in their 98 | // request context in Ctx.ClientName. 99 | // 100 | // You won't need this in your services/servers/tests. This function is public 101 | // only to make nano hackable for experiments. 102 | // You can replace this function with your own implementation to change the 103 | // ClientSet implementation passed by NewServiceSet to ServiceInit.Init. 104 | var NewClientSet = func(ss ServiceSet, ownerName string) ClientSet { 105 | return &clientSet{ 106 | ss: ss, 107 | ownerName: ownerName, 108 | } 109 | } 110 | 111 | // NewClient creates a new Client object that can be used to send requests to 112 | // the svc service. The ownerName can be anything but it should be the name of 113 | // the service if the Client is created for a service. 114 | // 115 | // The returned Client belongs to the owner specified by ownerName and requests 116 | // made through the Client object will send the ownerName to the called services 117 | // which can inspect ownerName in their request context in Ctx.ClientName. 118 | // 119 | // You won't need this in your services/servers/tests. This function is public 120 | // only to make nano hackable for experiments. 121 | // You can replace this function with your own implementation to change the 122 | // Client implementation returned by ClientSet. 123 | var NewClient = func(svc Service, ownerName string) Client { 124 | return &client{ 125 | svc: svc, 126 | ownerName: ownerName, 127 | } 128 | } 129 | 130 | // GeneratedReqIDBytesLen is the number of random bytes included in newly 131 | // generated RequestIDs returned by the default implementation of NewReqID. 132 | // Note that NewReqID returns a string that is the hex representation of the 133 | // random generated ReqID byte array so the length of the returned ReqID string 134 | // is the double of this value. 135 | var GeneratedReqIDBytesLen = 16 136 | 137 | // NewReqID generates a new random request ID. The default implementation 138 | // generates a random byte array of length defined by GeneratedReqIDBytesLen and 139 | // converts the byte array into a hex string. 140 | // 141 | // You won't need this in your services/servers/tests. This function is public 142 | // only to make nano hackable for experiments. 143 | var NewReqID = func() (string, error) { 144 | reqIDBytes := make([]byte, GeneratedReqIDBytesLen) 145 | _, err := rand.Read(reqIDBytes) 146 | if err != nil { 147 | return "", fmt.Errorf("error generating request ID :: %v", err) 148 | } 149 | return fmt.Sprintf("%x", reqIDBytes), nil 150 | } 151 | 152 | // NewContext has to return a new context.Context object and a related 153 | // cancel func for a new request. The returned cancel func is guaranteed to be 154 | // called exactly once, it doesn't have to handle multiple calls like the cancel 155 | // funcs returned by the standard context package. 156 | // 157 | // The clientContext parameter might be nil if the initiator of the request 158 | // isn't a service (e.g.: test) or if there is a transport layer between the 159 | // caller and the called service. If both services reside in the same server 160 | // executable then clientContext isn't nil but even in this case it is a 161 | // bad idea to make clientContext the parent of the newly created context 162 | // because that behavior would be very different from the scenario in which 163 | // there is a transport layer between the services. However it completely makes 164 | // sense to propagate the deadline and the cancellation of clientContext to the 165 | // newly created context. 166 | // 167 | // You won't need this in your services/servers/tests. This function is public 168 | // only to make nano hackable for experiments. 169 | var NewContext = func(clientContext context.Context) (context.Context, context.CancelFunc) { 170 | c := context.Background() 171 | if clientContext == nil { 172 | return context.WithCancel(c) 173 | } 174 | 175 | var cancel context.CancelFunc 176 | if deadline, ok := clientContext.Deadline(); ok { 177 | c, cancel = context.WithDeadline(c, deadline) 178 | } else { 179 | c, cancel = context.WithCancel(c) 180 | } 181 | 182 | cancelChan := make(chan struct{}) 183 | go func() { 184 | select { 185 | case <-clientContext.Done(): 186 | case <-cancelChan: 187 | } 188 | cancel() 189 | }() 190 | return c, func() { close(cancelChan) } 191 | } 192 | 193 | // serviceSet implements the ServiceSet interface. 194 | type serviceSet map[string]Service 195 | 196 | func (p serviceSet) LookupService(svcName string) (Service, error) { 197 | svc, ok := p[svcName] 198 | if !ok { 199 | return nil, fmt.Errorf("service not found: %v", svcName) 200 | } 201 | return svc, nil 202 | } 203 | 204 | // clientSet implements the ClientSet interface. 205 | type clientSet struct { 206 | ss ServiceSet 207 | ownerName string 208 | } 209 | 210 | func (p *clientSet) LookupClient(svcName string) Client { 211 | svc, err := p.ss.LookupService(svcName) 212 | if err != nil { 213 | panic(fmt.Sprintf("service %q failed to lookup client %q :: %v", 214 | p.ownerName, svcName, err)) 215 | } 216 | return NewClient(svc, p.ownerName) 217 | } 218 | 219 | // client implements the Client interface. 220 | type client struct { 221 | svc Service 222 | ownerName string 223 | } 224 | 225 | func (p *client) Request(c *Ctx, req interface{}) (resp interface{}, err error) { 226 | var c2 Ctx 227 | if c != nil { 228 | c2 = *c 229 | } 230 | 231 | if c2.ReqID == "" { 232 | if c2.ReqID, err = NewReqID(); err != nil { 233 | return nil, err 234 | } 235 | } 236 | 237 | var cancel context.CancelFunc 238 | c2.Context, cancel = NewContext(c2.Context) 239 | defer cancel() 240 | 241 | c2.Svc, c2.ClientName = p.svc, p.ownerName 242 | return p.svc.Handle(&c2, req) 243 | } 244 | -------------------------------------------------------------------------------- /nano_interfaces.go: -------------------------------------------------------------------------------- 1 | package nano 2 | 3 | import "context" 4 | 5 | // Listener is a component in the server executable that can receive requests 6 | // from the outside world and forward them to the already initialised services 7 | // located inside the server executable. 8 | type Listener interface { 9 | // Init is called with the initialised services of the server. 10 | Init(ServiceSet) error 11 | 12 | // Listen is called after Init. This method is called on its own goroutine, 13 | // this is how a server can listen on more than one listeners in parallel. 14 | Listen() error 15 | } 16 | 17 | // ServiceSet is a set of initialised services. During initialisation the 18 | // services have already resolved the dependencies between each other. 19 | type ServiceSet interface { 20 | // LookupService returns the service with the specified name. 21 | LookupService(svcName string) (Service, error) 22 | } 23 | 24 | // Service is an interface that has to be implemented by all services. 25 | type Service interface { 26 | // Name returns the name of the service. I recommend using simple names with 27 | // safe characters, snake_case is a very good choice. If you use simple 28 | // names then it will be easy to use the same name for directories, packages 29 | // host names, identifiers in code, etc... 30 | Name() string 31 | 32 | // Handle is the handler function of the service. It can receive any type 33 | // of request object and respond with any type of response. 34 | // If the type of the req parameter can't be handled by Handler then it 35 | // it should return a nano.BadReqTypeError. 36 | // Note that nil is a valid response value if you define the API of your 37 | // service that way. 38 | // 39 | // While there are no restrictions on the type of request and response 40 | // objects it is wise to use easy-to-serialize types (e.g.: json/protobuf 41 | // serializable types) in order to make it easy to transfer req/resp objects 42 | // through the wire. I recommend using struct pointers. 43 | Handle(c *Ctx, req interface{}) (resp interface{}, err error) 44 | } 45 | 46 | // ServiceInit is an interface that can optionally be implemented by a Service 47 | // object. If a service implements this interface then it receives an Init call 48 | // when the ServiceSet that contains this service is being created. 49 | type ServiceInit interface { 50 | // Init is called when the ServiceSet is being created. This is the right 51 | // time to resolve the dependencies of this service by asking for the 52 | // clients of other services from the received ClientSet. It is discouraged 53 | // to store the received ClientSet for later use - use it only before 54 | // returning from Init. 55 | // 56 | // Note that when your Init is called other services might be uninitialised 57 | // so you shouldn't send requests to them through the clients your obtained 58 | // using the ClientSet parameter. If you have to send a request to 59 | // another service at init time then you should implement the 60 | // ServiceInitFinished interface. 61 | // 62 | // Init is guaranteed to be called before the InitFinished and Handler 63 | // methods of any service in the ServiceSet that is being created. 64 | Init(ClientSet) error 65 | } 66 | 67 | // ServiceInitFinished is an interface that can optionally be implemented by a 68 | // Service object. If a service implements this interface then it receives an 69 | // InitFinished call when the ServiceSet that contains this service is being 70 | // created. 71 | type ServiceInitFinished interface { 72 | // InitFinished is called during the creation of a ServiceSet only after 73 | // calling the Init of all services in the ServiceSet. 74 | // 75 | // InitFinished is guaranteed to be called after the Init of your service 76 | // but it isn't guaranteed to be called before the Handler method because 77 | // the InitFinished of other services might be called earlier than your 78 | // InitFinished and they might send requests to your service. 79 | InitFinished() error 80 | } 81 | 82 | // ClientSet can be used by a service to obtain client interfaces to other 83 | // services. 84 | type ClientSet interface { 85 | // LookupClient returns a client interface to the service identified by the 86 | // given svcName. Trying to lookup a service that doesn't exist results in 87 | // a panic. This is by design to make the initialisation code simpler. 88 | // Normally services lookup their client at server startup in their Init 89 | // methods where a panic is acceptable. 90 | LookupClient(svcName string) Client 91 | } 92 | 93 | // Client is an interface that can be used to send requests to a service. 94 | type Client interface { 95 | // Requests sends a request to the service targeted by this client object. 96 | // The c parameter is the context of the caller service but it is allowed 97 | // to be nil or a Ctx instance with only some of its fields set. 98 | // 99 | // Note the similarity between the signature of Client.Request and 100 | // Service.Handle. The reason for calling another service through a Client 101 | // interface instead of directly calling its Service.Handle method is that 102 | // the request context (the c parameter) isn't directly passed between 103 | // services. While all other parameters and return values are transferred 104 | // directly between Client.Request and Service.Handle a new request context 105 | // (*Ctx) has to be created when the control is transferred to another 106 | // service. While it might make sense to directly copy and pass some fields 107 | // of the request context of the caller (e.g.: Ctx.ReqID), most other fields 108 | // of the request context have to be adjusted (e.g.: Ctx.Svc) that is done 109 | // by this Client interface. 110 | // 111 | // To clarify things: The c parameter of Client.Request is the request 112 | // context of the caller, while the c parameter of the Service.Handler will 113 | // be another request context newly created for the called service. 114 | Request(c *Ctx, req interface{}) (resp interface{}, err error) 115 | } 116 | 117 | // Ctx holds context data for a given request being served by a service. 118 | // If you fork and modify this repo (or roll your own stuff) for a project then 119 | // one of the benefits is being able to specify the contents of your request 120 | // context structure that is being passed around in your service functions. 121 | // I've included a few sensible fields but your project might want to exclude 122 | // some of these or perhaps add new ones. An example: If you implement 123 | // authentication and authorization at the transport layer then you can use a 124 | // field in the Ctx structure to pass user id info to the business logic if it 125 | // makes sense in the given project. 126 | type Ctx struct { 127 | // ReqID is the ID of the request that entered the cluster. Note that by 128 | // making calls to other services by calling Client.Request and passing this 129 | // context info the request inside the other service will copy this ReqID. 130 | // For this reason ReqID works as a correlation ID between the requests 131 | // handled by a cluster of your services and can be used for example to 132 | // track logs. 133 | ReqID string 134 | 135 | // Context can be useful when you call some std library functions that 136 | // accept a context. 137 | Context context.Context 138 | 139 | // Svc is the current service. 140 | Svc Service 141 | 142 | // ClientName is the name of the entity that initiated the request. It is 143 | // usually the name of another service but it can be anything else, for 144 | // example "test" if the request has been initiated by a test case. 145 | ClientName string 146 | } 147 | 148 | // WithContext returns a shallow copy of the context after assigning the given 149 | // ctx to the Context field. 150 | func (c *Ctx) WithContext(ctx context.Context) *Ctx { 151 | if ctx == nil { 152 | panic("nil context") 153 | } 154 | c2 := *c 155 | c2.Context = ctx 156 | return &c2 157 | } 158 | -------------------------------------------------------------------------------- /nano_test.go: -------------------------------------------------------------------------------- 1 | package nano 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | // testSvc implements the Service interface. 14 | type testSvc struct { 15 | name string 16 | handler func(c *Ctx, req interface{}) (interface{}, error) 17 | } 18 | 19 | func (p *testSvc) Name() string { 20 | return p.name 21 | } 22 | 23 | func (p *testSvc) Handle(c *Ctx, req interface{}) (interface{}, error) { 24 | return p.handler(c, req) 25 | } 26 | 27 | // testSvcInit implements the ServiceInit interface. 28 | type testSvcInit func(cs ClientSet) error 29 | 30 | func (f testSvcInit) Init(cs ClientSet) error { 31 | return f(cs) 32 | } 33 | 34 | // testSvcInitFinished implements the ServiceInitFinished interface. 35 | type testSvcInitFinished func() error 36 | 37 | func (f testSvcInitFinished) InitFinished() error { 38 | return f() 39 | } 40 | 41 | // testListener implements the Listener interface. 42 | type testListener struct { 43 | init func(ServiceSet) error 44 | listen func() error 45 | } 46 | 47 | func (p *testListener) Init(ss ServiceSet) error { 48 | if p.init == nil { 49 | return nil 50 | } 51 | return p.init(ss) 52 | } 53 | 54 | func (p *testListener) Listen() error { 55 | if p.listen == nil { 56 | return nil 57 | } 58 | return p.listen() 59 | } 60 | 61 | // eventSetEquals check if the events and want arrays contain the same set of 62 | // strings ignoring the ordering. 63 | func eventSetEquals(t *testing.T, events []string, want ...string) bool { 64 | if len(events) != len(want) { 65 | t.Errorf("event sets differ: events=%v, want=%v", events, want) 66 | return false 67 | } 68 | eventSet := make(map[string]struct{}, len(events)) 69 | for _, e := range events { 70 | eventSet[e] = struct{}{} 71 | } 72 | for _, e := range want { 73 | if _, ok := eventSet[e]; !ok { 74 | t.Errorf("event sets differ: events=%v, want=%v", events, want) 75 | return false 76 | } 77 | } 78 | return true 79 | } 80 | 81 | // eventSetContains check if the specified subset is contained by the events array. 82 | func eventSetContains(t *testing.T, events []string, subset ...string) bool { 83 | if len(events) < len(subset) { 84 | t.Errorf("event set doesn't contain subset: events=%v, subset=%v", events, subset) 85 | return false 86 | } 87 | eventSet := make(map[string]struct{}, len(events)) 88 | for _, e := range events { 89 | eventSet[e] = struct{}{} 90 | } 91 | for _, e := range subset { 92 | if _, ok := eventSet[e]; !ok { 93 | t.Errorf("event set doesn't contain subset: events=%v, subset=%v", events, subset) 94 | return false 95 | } 96 | } 97 | return true 98 | } 99 | 100 | func TestNewServiceSet(t *testing.T) { 101 | var events []string 102 | evt := func(e string) { 103 | events = append(events, e) 104 | } 105 | 106 | handler := func(c *Ctx, req interface{}) (interface{}, error) { 107 | evt("Handler:" + c.Svc.Name()) 108 | return nil, nil 109 | } 110 | 111 | svc1 := &testSvc{ 112 | name: "svc1", 113 | handler: handler, 114 | } 115 | 116 | svc2 := &struct { 117 | testSvc 118 | testSvcInit 119 | }{ 120 | testSvc: testSvc{ 121 | name: "svc2", 122 | handler: handler, 123 | }, 124 | testSvcInit: func(cs ClientSet) error { 125 | evt("Init:svc2") 126 | return nil 127 | }, 128 | } 129 | 130 | svc3 := &struct { 131 | testSvc 132 | testSvcInitFinished 133 | }{ 134 | testSvc: testSvc{ 135 | name: "svc3", 136 | handler: handler, 137 | }, 138 | testSvcInitFinished: func() error { 139 | evt("InitFinished:svc3") 140 | return nil 141 | }, 142 | } 143 | 144 | svc4 := &struct { 145 | testSvc 146 | testSvcInit 147 | testSvcInitFinished 148 | }{ 149 | testSvc: testSvc{ 150 | name: "svc4", 151 | handler: handler, 152 | }, 153 | testSvcInit: func(cs ClientSet) error { 154 | evt("Init:svc4") 155 | return nil 156 | }, 157 | testSvcInitFinished: func() error { 158 | evt("InitFinished:svc4") 159 | return nil 160 | }, 161 | } 162 | 163 | // create and initialise the service set 164 | _ = NewServiceSet(svc2, svc4, svc1, svc3) 165 | 166 | if v, want := len(events), 4; v != want { 167 | t.Errorf("number of events is %v, want %v - events: %v", v, want, events) 168 | } 169 | eventSetEquals(t, events[0:2], "Init:svc2", "Init:svc4") 170 | eventSetEquals(t, events[2:4], "InitFinished:svc3", "InitFinished:svc4") 171 | } 172 | 173 | func TestNewServiceSet_Initialisation_Calls_NewClientSet(t *testing.T) { 174 | var events []string 175 | origNewClientSet := NewClientSet 176 | defer func() { 177 | NewClientSet = origNewClientSet 178 | }() 179 | NewClientSet = func(ss ServiceSet, ownerName string) ClientSet { 180 | events = append(events, ownerName) 181 | return origNewClientSet(ss, ownerName) 182 | } 183 | 184 | handler := func(c *Ctx, req interface{}) (interface{}, error) { 185 | return nil, nil 186 | } 187 | init := func(cs ClientSet) error { 188 | return nil 189 | } 190 | 191 | svc1 := &struct { 192 | testSvc 193 | testSvcInit 194 | }{ 195 | testSvc: testSvc{ 196 | name: "svc1", 197 | handler: handler, 198 | }, 199 | testSvcInit: init, 200 | } 201 | 202 | svc2 := &struct { 203 | testSvc 204 | testSvcInit 205 | }{ 206 | testSvc: testSvc{ 207 | name: "svc2", 208 | handler: handler, 209 | }, 210 | testSvcInit: init, 211 | } 212 | 213 | _ = NewServiceSet(svc2, svc1) 214 | 215 | // Calling NewServiceSet should involve at least two calls to NewClientSet 216 | // for the svc1 and svc2 services. 217 | eventSetContains(t, events, "svc1", "svc2") 218 | } 219 | 220 | func TestClientSet_LookupClient_Calls_NewClient(t *testing.T) { 221 | var svcs []Service 222 | var owners []string 223 | origNewClient := NewClient 224 | defer func() { 225 | NewClient = origNewClient 226 | }() 227 | NewClient = func(svc Service, ownerName string) Client { 228 | svcs = append(svcs, svc) 229 | owners = append(owners, ownerName) 230 | return origNewClient(svc, ownerName) 231 | } 232 | 233 | svc1 := &testSvc{ 234 | name: "svc1", 235 | } 236 | ss := NewServiceSet(svc1) 237 | cs := NewClientSet(ss, "test") 238 | 239 | lenBeforeLookup := len(svcs) 240 | _ = cs.LookupClient("svc1") 241 | 242 | found := false 243 | for i := lenBeforeLookup; i < len(svcs); i++ { 244 | if svcs[i] == svc1 && owners[i] == "test" { 245 | found = true 246 | break 247 | } 248 | } 249 | 250 | if !found { 251 | t.Error("ClientSet.LookupClient didn't call NewClient") 252 | } 253 | } 254 | 255 | func TestServiceSet_LookupService(t *testing.T) { 256 | svc1 := &testSvc{ 257 | name: "svc1", 258 | } 259 | svc2 := &testSvc{ 260 | name: "svc2", 261 | } 262 | 263 | ss := NewServiceSet(svc1, svc2) 264 | 265 | _, err := ss.LookupService("svc1") 266 | if err != nil { 267 | t.Errorf("error looking up svc1 :: %v", err) 268 | } 269 | 270 | _, err = ss.LookupService("svc2") 271 | if err != nil { 272 | t.Errorf("error looking up svc2 :: %v", err) 273 | } 274 | 275 | _, err = ss.LookupService("svc3") 276 | if err == nil { 277 | t.Error("unexpected success while looking up svc3") 278 | } 279 | } 280 | 281 | func TestClient_Request_Nil_Ctx(t *testing.T) { 282 | const ownerName = "test" 283 | 284 | called := false 285 | var ctx *Ctx 286 | 287 | svc1 := &testSvc{ 288 | name: "svc1", 289 | handler: func(c *Ctx, req interface{}) (interface{}, error) { 290 | called = true 291 | ctx = c 292 | return nil, nil 293 | }, 294 | } 295 | 296 | ss := NewServiceSet(svc1) 297 | cs := NewClientSet(ss, ownerName) 298 | client := cs.LookupClient("svc1") 299 | _, _ = client.Request(nil, nil) 300 | 301 | if !called { 302 | t.Error("client wasn't called") 303 | t.FailNow() 304 | } 305 | if ctx == nil { 306 | t.Error("ctx is nil") 307 | t.FailNow() 308 | } 309 | if len(ctx.ReqID) != GeneratedReqIDBytesLen*2 { 310 | t.Errorf("len(ctx.ReqID) == %v, want %v", len(ctx.ReqID), GeneratedReqIDBytesLen*2) 311 | } 312 | if ctx.Context == nil { 313 | t.Error("ctx.Context is nil") 314 | } 315 | if ctx.Svc != svc1 { 316 | t.Error("ctx.Svc isn't set correctly") 317 | } 318 | if ctx.ClientName != ownerName { 319 | t.Errorf("ctx.ClientName == %q, want %q", ctx.ClientName, ownerName) 320 | } 321 | } 322 | 323 | func TestClient_Request_Empty_Ctx(t *testing.T) { 324 | const ownerName = "test" 325 | 326 | called := false 327 | var ctx *Ctx 328 | 329 | svc1 := &testSvc{ 330 | name: "svc1", 331 | handler: func(c *Ctx, req interface{}) (interface{}, error) { 332 | called = true 333 | ctx = c 334 | return nil, nil 335 | }, 336 | } 337 | 338 | ss := NewServiceSet(svc1) 339 | cs := NewClientSet(ss, ownerName) 340 | client := cs.LookupClient("svc1") 341 | _, _ = client.Request(&Ctx{}, nil) 342 | 343 | if !called { 344 | t.Error("client wasn't called") 345 | t.FailNow() 346 | } 347 | if ctx == nil { 348 | t.Error("ctx is nil") 349 | t.FailNow() 350 | } 351 | if len(ctx.ReqID) != GeneratedReqIDBytesLen*2 { 352 | t.Errorf("len(ctx.ReqID) == %v, want %v", len(ctx.ReqID), GeneratedReqIDBytesLen*2) 353 | } 354 | if ctx.Context == nil { 355 | t.Error("ctx.Context is nil") 356 | } 357 | if ctx.Svc != svc1 { 358 | t.Error("ctx.Svc isn't set correctly") 359 | } 360 | if ctx.ClientName != ownerName { 361 | t.Errorf("ctx.ClientName == %q, want %q", ctx.ClientName, ownerName) 362 | } 363 | } 364 | 365 | func TestClient_Request_With_Ctx(t *testing.T) { 366 | const ownerName = "test" 367 | 368 | called := false 369 | var ctx *Ctx 370 | 371 | svc1 := &testSvc{ 372 | name: "svc1", 373 | handler: func(c *Ctx, req interface{}) (interface{}, error) { 374 | called = true 375 | ctx = c 376 | return nil, nil 377 | }, 378 | } 379 | 380 | ss := NewServiceSet(svc1) 381 | cs := NewClientSet(ss, ownerName) 382 | client := cs.LookupClient("svc1") 383 | 384 | myContext := context.Background() 385 | const reqID = "MyReqID" 386 | inputCtx := &Ctx{ 387 | ReqID: reqID, 388 | Context: myContext, 389 | Svc: &testSvc{name: "ignored"}, 390 | ClientName: "ignored", 391 | } 392 | _, _ = client.Request(inputCtx, nil) 393 | 394 | if !called { 395 | t.Error("client wasn't called") 396 | t.FailNow() 397 | } 398 | if ctx == nil { 399 | t.Error("ctx is nil") 400 | t.FailNow() 401 | } 402 | if ctx.ReqID != reqID { 403 | t.Errorf("ctx.ReqID == %q, want %q", ctx.ReqID, reqID) 404 | } 405 | if ctx.Context == nil { 406 | t.Error("ctx.Context is nil") 407 | } else if ctx.Context == myContext { 408 | t.Error("ctx.Context shouldn't be the same as myContext") 409 | } 410 | if ctx.Svc != svc1 { 411 | t.Error("ctx.Svc isn't set correctly") 412 | } 413 | if ctx.ClientName != ownerName { 414 | t.Errorf("ctx.ClientName == %q, want %q", ctx.ClientName, ownerName) 415 | } 416 | } 417 | 418 | func testNewReqID(t *testing.T, generatedReqIDBytesLen int) { 419 | origLen := GeneratedReqIDBytesLen 420 | defer func() { 421 | GeneratedReqIDBytesLen = origLen 422 | }() 423 | GeneratedReqIDBytesLen = generatedReqIDBytesLen 424 | 425 | reqID, err := NewReqID() 426 | if err != nil { 427 | t.Errorf("NewReqID failed :: %v", err) 428 | t.FailNow() 429 | } 430 | if len(reqID) != GeneratedReqIDBytesLen*2 { 431 | t.Errorf("len(reqID) == %v, want %v", len(reqID), GeneratedReqIDBytesLen*2) 432 | } 433 | } 434 | 435 | func TestNewReqID(t *testing.T) { 436 | testNewReqID(t, GeneratedReqIDBytesLen) 437 | } 438 | 439 | func TestNewReqID_With_Custom_GeneratedReqIDBytesLen(t *testing.T) { 440 | testNewReqID(t, 5) 441 | } 442 | 443 | func isContextCanceled(c context.Context) bool { 444 | select { 445 | case <-c.Done(): 446 | return true 447 | default: 448 | return false 449 | } 450 | } 451 | 452 | const waitAttempts = 100 453 | const waitDuration = time.Millisecond * 1 454 | 455 | func waitForCancel(t *testing.T, c context.Context) bool { 456 | for i := 0; i < waitAttempts; i++ { 457 | if isContextCanceled(c) { 458 | return true 459 | } 460 | time.Sleep(waitDuration) 461 | } 462 | return false 463 | } 464 | 465 | func TestNewContext_Nil_Parent(t *testing.T) { 466 | c, origCancel := NewContext(nil) 467 | cancel := func() { 468 | if origCancel != nil { 469 | origCancel() 470 | origCancel = nil 471 | } 472 | } 473 | defer cancel() 474 | 475 | if isContextCanceled(c) { 476 | t.Error("context is canceled right after creation") 477 | } 478 | 479 | cancel() 480 | if !waitForCancel(t, c) { 481 | t.Error("context isn't canceled after canceling it") 482 | } 483 | } 484 | 485 | func TestNewContext_With_Parent(t *testing.T) { 486 | parentContext, parentCancel := context.WithCancel(context.Background()) 487 | defer parentCancel() 488 | 489 | c, origCancel := NewContext(parentContext) 490 | cancel := func() { 491 | if origCancel != nil { 492 | origCancel() 493 | origCancel = nil 494 | } 495 | } 496 | defer cancel() 497 | 498 | if isContextCanceled(c) { 499 | t.Error("context is canceled right after creation") 500 | } 501 | 502 | cancel() 503 | if !waitForCancel(t, c) { 504 | t.Error("context isn't canceled after canceling it") 505 | } 506 | if isContextCanceled(parentContext) { 507 | t.Error("parent is canceled") 508 | } 509 | } 510 | 511 | func TestNewContext_With_Parent_Cancel(t *testing.T) { 512 | parentContext, parentCancel := context.WithCancel(context.Background()) 513 | defer parentCancel() 514 | 515 | c, cancel := NewContext(parentContext) 516 | defer cancel() 517 | 518 | if isContextCanceled(c) { 519 | t.Error("context is canceled right after creation") 520 | } 521 | 522 | parentCancel() 523 | if !waitForCancel(t, c) { 524 | t.Error("context isn't canceled after canceling it") 525 | } 526 | if !waitForCancel(t, parentContext) { 527 | t.Error("parent isn't canceled") 528 | } 529 | } 530 | 531 | func TestRunServer(t *testing.T) { 532 | var events []string 533 | var mu sync.Mutex 534 | evt := func(e string) { 535 | // Synchronization is needed because the Listen() method of Listeners 536 | // is called on separate goroutines. 537 | mu.Lock() 538 | defer mu.Unlock() 539 | events = append(events, e) 540 | } 541 | 542 | onError := func(l Listener, err error) { 543 | t.Errorf("listener %p failed :: %v", l, err) 544 | } 545 | ss := NewServiceSet(&testSvc{ 546 | name: "svc1", 547 | }) 548 | 549 | listener1 := &testListener{ 550 | init: func(s ServiceSet) error { 551 | if _, err := s.LookupService("svc1"); err != nil { 552 | t.Error("error looking up svc1") 553 | } 554 | evt("Init:listener1") 555 | return nil 556 | }, 557 | listen: func() error { 558 | evt("Listen:listener1") 559 | return nil 560 | }, 561 | } 562 | listener2 := &testListener{ 563 | init: func(s ServiceSet) error { 564 | if _, err := s.LookupService("svc1"); err != nil { 565 | t.Error("error looking up svc1") 566 | } 567 | evt("Init:listener2") 568 | return nil 569 | }, 570 | listen: func() error { 571 | evt("Listen:listener2") 572 | return nil 573 | }, 574 | } 575 | 576 | runServer(onError, ss, listener2, listener1) 577 | 578 | if v, want := len(events), 4; v != want { 579 | t.Errorf("number of events is %v, want %v - events: %v", v, want, events) 580 | } 581 | eventSetEquals(t, events[0:2], "Init:listener1", "Init:listener2") 582 | eventSetEquals(t, events[2:4], "Listen:listener1", "Listen:listener2") 583 | } 584 | 585 | func TestRunServer_Listener_Init_Error(t *testing.T) { 586 | onError := func(l Listener, err error) { 587 | t.Errorf("listener %p failed :: %v", l, err) 588 | } 589 | ss := NewServiceSet(&testSvc{ 590 | name: "svc1", 591 | }) 592 | e := errors.New("listener init error") 593 | called := false 594 | listener := &testListener{ 595 | init: func(s ServiceSet) error { 596 | called = true 597 | return e 598 | }, 599 | } 600 | 601 | defer func() { 602 | if !called { 603 | t.Error("listener init wasn't called") 604 | } 605 | v := recover() 606 | if v == nil { 607 | t.Error("haven't received the expected panic") 608 | } else { 609 | s := fmt.Sprintf("%v", v) 610 | if !strings.Contains(s, e.Error()) { 611 | t.Error("panic message doesn't contain the error message") 612 | } 613 | } 614 | }() 615 | 616 | runServer(onError, ss, listener) 617 | } 618 | 619 | func TestRunServer_Listen_Error(t *testing.T) { 620 | e := errors.New("listen error") 621 | called := false 622 | onError := func(l Listener, err error) { 623 | called = true 624 | if err != e { 625 | t.Errorf("listener %p failed with unexpected error :: %v", l, err) 626 | } 627 | } 628 | 629 | ss := NewServiceSet(&testSvc{ 630 | name: "svc1", 631 | }) 632 | listener := &testListener{ 633 | listen: func() error { 634 | return e 635 | }, 636 | } 637 | 638 | runServer(onError, ss, listener) 639 | 640 | if !called { 641 | t.Error("onError wasn't called") 642 | } 643 | } 644 | --------------------------------------------------------------------------------