Compare commits

..

309 Commits

Author SHA1 Message Date
Asim Aslam f12473f4b1 Update runtime.go
Docker / build (push) Waiting to run
Run tests / Test repo (push) Waiting to run
2020-09-04 22:43:32 +01:00
Dominic Wong 724e2b5830 Memory events stream not pushing publications correctly (#1984)
* subs not filtered correctly on publish. simplify retry logic

* check fo invalid ackwait
2020-09-04 08:31:49 +01:00
Dominic Wong 6bdf33c4ee Event stream updates (#1981)
- auto and manual acking
- retry limits
2020-09-02 13:28:54 +01:00
dy1006 84f52fd7ac Update log.go (#1976)
change nlog.DefaultLogger.Log to nlog.DefaultLogger.Logf in the Logf function
2020-08-31 07:18:55 +01:00
Dominic Wong 6e30b53280 fix cockroach init to create table in correct database (#1977) 2020-08-28 10:35:47 +01:00
dy1006 a60426c884 Update rpc.go (#1975)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-08-27 10:24:19 +01:00
Prawn 2998735bf3 Tidying up the new Metrics implementations (#1974)
* Unit tests to check tagging and aggregation of Prometheus metrics

* Removing the logger output routing (because it doesn't actually work in the logger implementation)

* Emitting values with the logging reporter

Co-authored-by: chris <chris@Profanity.local>
2020-08-27 09:08:51 +01:00
wangxu 3a96135df8 add log grpc handler err (#1973)
Co-authored-by: wangxu <wangxu@oneniceapp.com>
2020-08-25 10:11:02 +01:00
zuoan bf8b3aeac7 remove redunant code and cleanup (#1970)
* remove redundant code

* check invalid ip address first

* remove redundant code

* cleanup

Co-authored-by: 刘海洋 <haiyang@snqu.com>
2020-08-25 09:10:46 +01:00
Dominic Wong 5a52b5929c add create and delete namespace to runtime (#1965)
* add create and delete namespace to runtime

* dial down aggressive expiry

* add logging

* fix deletenamespace

* add start of k8s unit tests

* fix workflow

* turn on k8s tests

* ease tight tests

* mkdir in workflow

* dammit -p

* setup folder
2020-08-24 16:54:39 +01:00
ben-toogood 0adb469a85 runtime/local: fix unknown dir path (#1964)
Co-authored-by: Asim Aslam <asim@aslam.me>
Co-authored-by: Janos Dobronszki <dobronszki@gmail.com>
2020-08-24 15:23:44 +02:00
Asim Aslam 21004341bf Rename reporter.go to metrics.go 2020-08-23 22:18:28 +01:00
Asim Aslam cc26f2b8b1 delete api service proto 2020-08-23 21:58:16 +01:00
Asim Aslam 1a6652fe6b sql model template 2020-08-23 21:30:46 +01:00
Asim Aslam d28f0670d6 Move git into local/source 2020-08-23 21:23:07 +01:00
Asim Aslam 7bdd619e1b move plugin to util 2020-08-23 21:18:59 +01:00
Asim Aslam c62d1d5eb8 move transport (#1967) 2020-08-23 18:37:22 +01:00
Asim Aslam d60d85de5c move tunnel/resolver into network 2020-08-23 15:00:27 +01:00
Asim Aslam 44f281f8d9 remove test request file 2020-08-23 14:13:45 +01:00
Asim Aslam f698feac9c remove context from client/server 2020-08-23 14:11:57 +01:00
Asim Aslam f55701b374 Strip cache from the client. Its only used externally in a wrapper 2020-08-23 13:59:19 +01:00
Asim Aslam 82e8298b73 Router table.Read replaces List/Query (#1966)
* Table.REad insted of list and query

* fmt
2020-08-23 13:10:48 +01:00
Asim Aslam fc54503232 Merge branch 'master' of ssh://github.com/micro/go-micro 2020-08-22 20:55:52 +01:00
Asim Aslam 6f0594eebe Update static router 2020-08-22 20:55:43 +01:00
Asim Aslam 6b52f859cf Update mucp.go 2020-08-22 16:36:03 +01:00
Asim Aslam a3d4b8f79b Update mucp.go 2020-08-22 16:35:12 +01:00
Asim Aslam 7c7df6b35d Remove quic transport, move route into router 2020-08-22 16:15:44 +01:00
Asim Aslam e80eab397a Update events.go 2020-08-22 09:20:53 +01:00
ben-toogood 6cda6ef92e runtime/local: add support for idiomatic folder structures (#1963)
* runtime/local: add support for idiomatic folder structures

* runtime/local: add test coverage

* runtime/local: increase test coverage

* runtime/local: add test for empty local source

* runtime/local: make entrypoint public
2020-08-21 11:17:42 +01:00
Prawn f9f61d29de Observability/metrics update (#1962)
* Removing logging from the NOOP implementatino

* Simplifying the percentiles option

* Simple logging implementation

Co-authored-by: chris <chris@Profanity.local>
2020-08-21 20:57:10 +12:00
ben-toogood 1ae825032c store: remove write TTL & expiry options (#1960) 2020-08-21 09:35:53 +01:00
Asim Aslam f146b52418 Registry router fixes (#1961)
* only cache routes if told to do so

* Use roundrobin selector and retry in proxy

* Update lookup to require service

* Fix compile

* Fix compile

* Update

* Update

* rename query to lookup

* Update router.go

* Update
2020-08-21 09:23:01 +01:00
Dominic Wong 78a79ca9e1 Memory and file store list fixes (#1959)
* Refactor file and memory stores
2020-08-20 15:08:35 +01:00
ben-toogood 329bc2f265 events: add store implementation (#1957) 2020-08-20 11:28:04 +01:00
Asim Aslam 8738ed7757 Update debug.go 2020-08-20 10:06:02 +01:00
ben-toogood 29e8cdbfe9 events: update interface (#1954) 2020-08-20 09:29:29 +01:00
Dominic Wong 47f356fc5f Unify the store tests (#1952)
Add more tests for store
2020-08-19 23:41:03 +01:00
Janos Dobronszki 21ffc73c4f Generic git checkout (#1951) 2020-08-19 17:24:42 +02:00
ben-toogood 81a9342b83 util/file: allow context to be passed (#1950) 2020-08-19 16:03:19 +01:00
ben-toogood 66df1bb361 events/nats: add support for TLS config (#1946)
* events/nats: add support for TLS config

* events/nats: improve error logging
2020-08-19 10:55:54 +01:00
Asim Aslam 7eaec450a1 support error handler in memory broker (#1947) 2020-08-19 10:20:43 +01:00
Asim Aslam 5d6b7b3d7d Move the network resolver out (#1944) 2020-08-18 21:38:29 +01:00
Dominic Wong 2eac8ed64f Fix cockroach store not respecting WriteTTL option (#1943)
* cockroach fixes for expiry

* cockroach should run in the background
2020-08-18 18:30:05 +01:00
Janos Dobronszki 2b2dc2f811 Support private repos in env 'local' (#1938) 2020-08-18 18:26:14 +02:00
ben-toogood 21cca297c0 events: implement package with memory & nats streams (#1942) 2020-08-18 16:19:53 +01:00
Asim Aslam 19ef225b2f Revert "grpc: avoid allocations for each message (#1939)" (#1941)
This reverts commit 2a23224d91.
2020-08-18 14:44:29 +01:00
Vasiliy Tolstov 2a23224d91 grpc: avoid allocations for each message (#1939)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-08-18 14:24:11 +01:00
Asim Aslam dd2dc7a2b9 Update broker.go 2020-08-18 14:05:25 +01:00
Asim Aslam 4413372a3f Decruft the broker by removing Event interface (#1940) 2020-08-18 14:00:51 +01:00
Janos Dobronszki a2a808f2d6 Baseurl, gitlab support, single word service names (#1933) 2020-08-18 11:31:49 +02:00
Asim Aslam 7a6669d199 Update reporter.go 2020-08-18 08:30:29 +01:00
Asim Aslam 09fdd3c121 Update reporter.go 2020-08-18 08:29:26 +01:00
Prawn da4159513e Metrics interface and Prometheus implementation (#1929)
* Metrics interface

* Prometheus implementation

* NoOp implementation

Co-authored-by: chris <chris@Profanity.local>
2020-08-18 08:27:50 +01:00
Asim Aslam e1248f90f4 update options 2020-08-17 23:09:41 +01:00
Asim Aslam 3011bad518 sort routes by metric 2020-08-17 23:09:24 +01:00
Asim Aslam bb7fe21c46 unused variables 2020-08-17 23:00:27 +01:00
Asim Aslam 4fd4a116f2 allow setting registry router in client 2020-08-17 22:53:20 +01:00
Asim Aslam 50ec6c748f cleanup client/selector/lookup (#1937)
* cleanup client/selector/lookup

* add mdns router, remove registry from client

* fix roundtripper

* remove comment

* fix compile issue

* fix mucp test

* fix api router
2020-08-17 22:44:45 +01:00
Asim Aslam 7135787b78 Update README.md 2020-08-17 19:43:28 +01:00
Asim Aslam 870a1ebc07 dead code 2020-08-17 10:04:52 +01:00
zuoan 61899398b3 simplifies code (#1934)
Co-authored-by: 刘海洋 <haiyang@snqu.com>
2020-08-17 11:10:42 +03:00
Asim Aslam 55d62fc1a5 Strip Advertise/Process from router 2020-08-14 23:51:52 +01:00
Asim Aslam 5238a8a85f don't delete own routes 2020-08-14 23:04:55 +01:00
Asim Aslam 9fffd0419e Merge branch 'master' of ssh://github.com/micro/go-micro 2020-08-14 21:52:18 +01:00
Asim Aslam 58794df27c default to noop registry in network 2020-08-14 21:52:05 +01:00
ben-toogood 5a88ea7247 runtime: resource limits (kubernetes implementation) (#1931)
* runtime: add resource limit CreateOptions

* util/kubernetes/client: implement support for resource limits

* runtime/kubernetes: set resource limits for k8s deployments

* util/kubernetes: remove template check for ints

* util/kubernetes: fix incorrect yaml syntax

* runtime/kubernetes: fix incorrect units

* runtime: update create options to use Resources struct
2020-08-14 11:47:28 +01:00
Asim Aslam 374aae1490 Merge branch 'master' of ssh://github.com/micro/go-micro 2020-08-13 14:10:49 +01:00
Asim Aslam ccf2f4efd6 fix windows 2020-08-13 14:10:41 +01:00
ben-toogood 9380b365de runtime/local: fix injection of secrets as env vars (#1930) 2020-08-13 09:22:25 +01:00
Asim Aslam f0142febcf executable is now os 2020-08-13 07:57:57 +01:00
Asim Aslam 1fa3ac5599 write nil when expiry is zero
Docker / build (push) Waiting to run
Run tests / Test repo (push) Waiting to run
2020-08-12 12:52:14 +01:00
Asim Aslam 375b67ee16 simplify runtime logs 2020-08-11 22:57:30 +01:00
Dominic Wong 69a53e8070 expiry can be taken from options or record (#1928) 2020-08-11 18:11:18 +01:00
Asim Aslam b6e1c7ac99 make source dir a variable 2020-08-11 17:25:43 +01:00
Asim Aslam e83a808b05 make log dir a variable 2020-08-11 17:23:00 +01:00
ben-toogood 012ec6a998 router/registry: fix expiring routes bug (#1927) 2020-08-11 16:57:04 +01:00
Asim Aslam fae4151027 Add a build package (#1926)
* Add a build package

* fix go mod

* package tar
2020-08-11 16:51:58 +01:00
ben-toogood e162e6d505 router/registry: fix bug which impacts service registered in multiple domains (#1925)
* router/registry: fix bug which impacts service registered in multiple domains

* router/registry: bugfix
2020-08-11 12:42:22 +01:00
Asim Aslam c51ef6fc29 move wrapper to client 2020-08-11 11:25:49 +01:00
Asim Aslam 28d6340f04 Merge branch 'master' of ssh://github.com/micro/go-micro 2020-08-11 10:32:25 +01:00
Asim Aslam 4fc193f95d Delete the cmd package 2020-08-11 10:24:55 +01:00
Asim Aslam d072eb6ff2 Deprecate service (#1924) 2020-08-11 10:03:47 +01:00
ben-toogood 1263806a39 util/kubernetes: add readiness check to deployments (#1923) 2020-08-11 08:38:30 +01:00
Asim Aslam 959407bad9 support wrapper slice in api server options 2020-08-10 22:38:54 +01:00
Asim Aslam 61d12d3a39 fix etcd keys for services (#1922) 2020-08-10 21:58:35 +01:00
Asim Aslam 4db8ea8f6a Move tunnel to its own package (#1921) 2020-08-10 17:31:21 +01:00
Asim Aslam 13f495587e cleanup debug and transport (#1920) 2020-08-10 15:58:39 +01:00
ben-toogood 593b543230 runtime/kubernetes: fix streaming logs error handling (#1919) 2020-08-10 15:20:33 +01:00
ben-toogood fdce953c15 runtime/kubernetes: fix update bug (#1918) 2020-08-10 15:08:04 +01:00
Janos Dobronszki 96836f2e43 Decrease log levels in router/registry package to not appear for CLI users (#1917) 2020-08-10 13:57:45 +01:00
Asim Aslam 65e6ee8566 use noop resolver in network by default 2020-08-09 22:11:57 +01:00
Asim Aslam a7c70c66b1 return a micro error on lookup failure 2020-08-09 21:44:39 +01:00
Asim Aslam b2582c0992 fix deadlock bug 2020-08-09 19:39:21 +01:00
Asim Aslam 6373cc91b7 remove print statement 2020-08-09 19:35:07 +01:00
Asim Aslam ed704640aa getDomain should return the default domain 2020-08-09 19:08:25 +01:00
Asim Aslam cd9e5a1e9e continue to allow endpoint routing 2020-08-09 16:57:34 +01:00
Asim Aslam dcf040ec9f strip back the grpc proxy 2020-08-09 16:47:00 +01:00
Asim Aslam f838c33008 noop.NewRegistry function 2020-08-09 16:26:51 +01:00
Asim Aslam e8ea0f85e9 add a noop registry 2020-08-09 16:17:52 +01:00
Asim Aslam 51f8b4ae3d embed grpc server stream and client so they can be accessed (#1916) 2020-08-09 15:43:41 +01:00
Asim Aslam 69a2032dd7 lower log level to debug 2020-08-08 14:04:18 +01:00
Asim Aslam 64feb6dff2 Add subscriber naem 2020-08-08 09:21:13 +01:00
Asim Aslam 4c95c65d81 Return service name in error 2020-08-08 09:09:34 +01:00
Asim Aslam 4469a41ae7 use a totally different client for the watcher in etcd 2020-08-08 01:40:41 +01:00
Asim Aslam fc67593ee4 cleanup router watcher logic 2020-08-08 01:04:38 +01:00
Asim Aslam e7cc3c2210 protect etcd watcher stop against race condition 2020-08-08 00:57:57 +01:00
Asim Aslam 712fe39a62 initChan is never evaluated because watchRegistry is a blocking call 2020-08-07 23:44:43 +01:00
Asim Aslam 9b14eb8aec close the existing etcd client if it exists 2020-08-07 23:09:06 +01:00
Asim Aslam 124b1bd7b7 add https prefix when using tls config for etcd 2020-08-07 22:46:05 +01:00
ben-toogood ac1aace214 route the API subdomain (#1910)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-08-07 20:54:55 +01:00
Asim Aslam 324c4e6886 Router refresh (#1912)
* checkpoint

* Refresh and prune routes periodically in the registry router

* remove comment
2020-08-07 20:53:38 +01:00
Asim Aslam d69a4a30cd fix etcd bug that causes deregister to be skipped (#1911) 2020-08-07 19:58:25 +01:00
Asim Aslam a6d7b1d710 Move api/router/util to util/router (#1909) 2020-08-07 13:30:29 +01:00
Asim Aslam 8ee31b94a1 remove handler/util package in favour of util/router (#1908) 2020-08-07 12:47:20 +01:00
Asim Aslam 37cc7fda92 Update pprof.go 2020-08-07 12:18:01 +01:00
Asim Aslam d61cbd29db Update README.md 2020-08-07 11:22:57 +01:00
Asim Aslam b6ab124d83 Update README.md 2020-08-07 11:21:59 +01:00
Dominic Wong 835343d6a5 logs should return for non existent services (#1906) 2020-08-06 22:56:05 +01:00
Maarten Bezemer 74907987d1 codec - Allow to Write() nil body (#1905)
* codec - Allow to Write() nil body

* Oops we are in v3 now
2020-08-06 18:51:00 +01:00
Asim Aslam fb8533b74e Update tunnel 2020-08-06 18:50:35 +01:00
Z dcf785677f Fix: file-watcher bugs (#1897)
* Fix: file-watcher bugs

* Update watcher_linux.go

Co-authored-by: 杨铭哲 <yangmz@weipaitang.com>
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-08-06 18:21:09 +01:00
ben-toogood 991cdba91d registry/etcd: fix services combining (#1901) 2020-08-06 15:38:50 +01:00
Asim Aslam aefd052dd7 Etcd router bug fixing etcd path prefix matching name (#1899)
* add logging and don't get nodes where they exist in router

* add more logging

* Fix the etcd bug for name matching of keys and prefixes matching names
2020-08-06 12:42:14 +01:00
Asim Aslam 2b79910ad9 add logging and don't get nodes where they exist in router (#1898)
* add logging and don't get nodes where they exist in router

* add more logging
2020-08-06 11:32:06 +01:00
Asim Aslam 8674dc8e62 Remove precache in favour of just pulling by default 2020-08-06 10:27:58 +01:00
Asim Aslam b93cd0c964 dont process endpoint unless absolutely necessary 2020-08-05 18:09:04 +01:00
Asim Aslam 39bd6a6ced skip watching routes if client proxy is set, remove later 2020-08-05 18:05:25 +01:00
Asim Aslam 03d47afe47 Fix proxy selector memory leak 2020-08-05 17:38:41 +01:00
ben-toogood 38ec233350 proxy/mucp: don't lookup routes if client using proxy (#1896) 2020-08-05 12:45:56 +01:00
Dominic Wong eee91ed976 Check chan not closed before sending updates for memory loader (#1894)
* dont send on closed chan
2020-08-04 16:21:03 +01:00
ben-toogood 07fef9fd33 router/registry: fix initialization bug (#1893) 2020-08-04 11:43:01 +01:00
Asim Aslam 1106f1d996 remove agent 2020-08-04 07:51:10 +01:00
Asim Aslam 8e126e4fc1 Remove go-micro/web 2020-08-04 07:47:20 +01:00
Asim Aslam 1439b101ec ensure register ttl and interval are set 2020-08-03 22:44:29 +01:00
Asim Aslam 24e5b2a034 Merge branch 'master' of ssh://github.com/micro/go-micro 2020-08-03 22:21:13 +01:00
Asim Aslam 971a962894 Fix typo 2020-08-03 22:20:30 +01:00
ben-toogood 31ed4aa0e8 registry/etcd: fix logging nil pointer dereference (#1889)
* registry/etcd: fix logging nil pointer dereference

* Fix stupid mistake

* Fix merge
2020-07-31 15:05:32 +01:00
ben-toogood d2cea4b7b7 registry/etcd: fix logging nil pointer dereference (#1888) 2020-07-31 14:40:11 +01:00
ben-toogood 0b73d411ad client: rename WithServiceToken => WithAuthToken (#1887) 2020-07-31 11:36:33 +01:00
ben-toogood 83a64797fb Fix proxy being overriden by default addresses (#1886) 2020-07-31 08:55:08 +01:00
ben-toogood e9fc5b1671 client: add proxy option (#1885)
* client: add proxy option

* client: add WithProxy CallOption

* use address option

* ProxyAddress => Proxy
2020-07-30 15:22:36 +01:00
ben-toogood 006bbefaf3 runtime: support for dynamic secrets (#1861)
* runtime: replace CreateCredentials with CreateSecret

* runtime/kubernetes: secrets support

* runtime: CreateSecret => WithSecret

* runtime: use map[string]string for secrets

* runtime/kubernetes: update to use kv secrets

* Fix merge conflict (missing import)

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-29 13:41:50 +01:00
Lars Lehtonen 3d1ba914fc tunnel: remove unused test loop (#1878)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-29 12:51:09 +01:00
yu d66803a136 fix bug https://github.com/micro/go-micro/issues/1883 (#1884)
* fix #1883

* fix #1883

Co-authored-by: 杨羽 <yangyu@doumi.com>
2020-07-29 12:45:25 +01:00
ben-toogood 9813f98c8b config: remove default config (#1882)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-28 13:54:58 +01:00
Vasiliy Tolstov c6163bb22f fix qson parsing on invalid input, close #1874 (#1880)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-07-28 12:34:50 +01:00
ben-toogood cb4a2864da router/registry: fix not started bug (#1877) 2020-07-28 09:01:08 +01:00
Asim Aslam f17e4fdb44 Update README.md (#1876) 2020-07-27 16:42:50 +01:00
Asim Aslam 563768b58a v3 refactor (#1868)
Docker / build (push) Waiting to run
Run tests / Test repo (push) Waiting to run
* Move to v3

Co-authored-by: Ben Toogood <bentoogood@gmail.com>
2020-07-27 13:22:00 +01:00
Dominic Wong 9dfeb98111 cockroach typo in init (#1872) 2020-07-27 10:16:40 +01:00
Asim Aslam 5f4491bb86 change network resolver http to localhost:8080 2020-07-26 11:21:57 +01:00
Janos Dobronszki fbdf1f2c1c Fixing micro logs being follow by default against k8s (#1866) 2020-07-23 09:50:38 +02:00
ben-toogood a3a7434f2c client/grpc: fix error panic (#1860) 2020-07-20 10:04:51 +01:00
Asim Aslam 592179c0a2 Remove deprecations since next release is v3 2020-07-19 19:59:33 +01:00
Asim Aslam 9b74bc52d6 Move defaults to defaults.go 2020-07-19 19:48:11 +01:00
Asim Aslam 05f3e1a125 Add cmd.Run function 2020-07-19 18:43:33 +01:00
Asim Aslam 16c591d741 call cmd.Run 2020-07-19 18:20:17 +01:00
Asim Aslam 755b816086 Use Run instead of Init for cmd 2020-07-19 18:14:18 +01:00
Asim Aslam 7aa92fa8b5 add String method to command 2020-07-19 17:20:05 +01:00
ijayer 5077683b70 refactor(logger): fix the name of defaultLogger receiver (#1859)
Closes #1858

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-19 16:55:50 +01:00
Asim Aslam 7f6cefd9c9 fix grpc test 2020-07-19 15:54:33 +01:00
Asim Aslam 647ce61dec some renaming of types in auth 2020-07-19 14:41:31 +01:00
Asim Aslam d3326efd4b Move out the token package to util 2020-07-19 13:41:23 +01:00
Asim Aslam 6920677f1e Move rules.Verify to auth.VerifyAccess 2020-07-19 13:12:03 +01:00
Asim Aslam 1838e4a1ee remove auth provider 2020-07-19 11:37:40 +01:00
Asim Aslam 96233b2d9b auth jwt / service package comments 2020-07-19 10:53:38 +01:00
Asim Aslam e082ac42a0 go fmt 2020-07-19 10:51:16 +01:00
Asim Aslam d7ef224447 add selector package comment 2020-07-19 10:51:04 +01:00
Asim Aslam 8c6f4062ef Remove error proto in favour of go type 2020-07-19 09:29:48 +01:00
ben-toogood 0d860c53a6 runtime/kubernetes: ignore namespace already exists errors (#1852)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-18 11:12:05 +01:00
Vasiliy Tolstov bcc890e47c router: pass node metadata to route instead of service metadata (#1855)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-07-17 16:45:22 +01:00
Di Wu f9bf562393 fix typo in comments (#1840)
* remove global error tracking

* rpc_server: fix invalid register err

* fix typo

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-16 16:33:11 +01:00
Vasiliy Tolstov dfa50a888d make LookupRoute exported (#1850)
* make LookupRoute exported

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* add missing file

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-07-16 16:32:37 +01:00
Asim Aslam e63b9015ae Add memcache implementation of cache (#1848) 2020-07-16 14:13:38 +01:00
Vasiliy Tolstov 3627e47f04 client/grpc: dont use codec for raw bytes payload (#1847)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-07-16 13:35:06 +01:00
Asim Aslam 7d41c2224e Don't close the stream (#1844) 2020-07-15 14:44:31 +01:00
ben-toogood 68927e875b store/service: use client passed in Init options (#1843) 2020-07-15 14:01:33 +01:00
ben-toogood 0c19a87c89 cmd/cmd: use service namespace as store database (#1842) 2020-07-15 11:31:42 +01:00
ben-toogood f73ec65ac3 runtime/kubernetes: increase debugging (#1841) 2020-07-15 09:26:25 +01:00
Asim Aslam b27e71ae64 rip out cmd.DefaultOptions 2020-07-14 22:23:54 +01:00
Asim Aslam 0299517f0d remove config readme 2020-07-14 21:55:05 +01:00
Asim Aslam e1404a1100 remove secrets package 2020-07-14 21:53:43 +01:00
Asim Aslam 057d61063f Move back command 2020-07-14 20:57:13 +01:00
ben-toogood 73a3f596e8 util/kubernetes/client: set imagePullPolicy to always (#1838) 2020-07-14 15:17:23 +01:00
ben-toogood 0287ab8751 auth/service/proto: add Delete RPC (#1836)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-14 13:41:35 +01:00
Alex Unger 42c28f2b6d Fix Comments (#1833)
* update store url

* fix leftover copy paste
2020-07-14 13:13:32 +01:00
ben-toogood a2bb0bea2d auth: add token issuer option (#1835) 2020-07-14 13:44:51 +02:00
ben-toogood 9f9c748f9b auth/service: fix account issuer bug (#1834) 2020-07-14 12:16:52 +01:00
ben-toogood a5e9dc21ca util/wrapper: allow enforcing a specific namespace when verifying requests (#1832)
* auth/jwt: add debugging

* auth: more debugging

* auth: more debugging

* util/wrapper: don't use request context

* util/wrapper: AuthHandlerNamespace

* remove debugging
2020-07-14 10:27:15 +01:00
Janos Dobronszki 3f4b58b58c Let bolt do locking per each List Get etc op, instead of managing fil… (#1831) 2020-07-14 10:35:46 +02:00
Asim Aslam 0a79db498c do not compare snapshot unless non nil (#1830) 2020-07-14 07:52:45 +01:00
Dominic Wong 7c5e3b0f30 report errors from log streaming (#1828) 2020-07-13 17:35:23 +01:00
ben-toogood 07fbb06ed8 auth/service: fix jwt blank refresh bug (#1827) 2020-07-13 17:12:03 +01:00
ben-toogood a4252ba69c router/registry: use warn error level (#1826) 2020-07-13 15:27:14 +01:00
ben-toogood 8fe4f1f2c3 config/cmd: don't fatally error if config can't be loaded (#1825)
* config/cmd: don't fatally error if config cannot be loaded

* config/cmd: fix log level typo
2020-07-13 15:08:53 +01:00
ben-toogood 2e04fcd718 config/cmd: don't fatally error if auth account cannot be generated (#1824) 2020-07-13 12:52:54 +01:00
ben-toogood 7355455020 auth/service: generate accounts client side if JWT credentials present (#1823) 2020-07-13 10:20:31 +01:00
Lars Lehtonen 040577fb74 transport/grpc: replace deprecated grpc.WithTimeout() (#1822) 2020-07-13 07:35:53 +01:00
ben-toogood 4e7621da18 config/cmd: standardise error handling (#1816)
* runtime/kubernetes: remove reference to runtime cells

* config/cmd: standardize error handling

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-12 07:22:08 +01:00
Huan Huang 8e30ede8c7 track error (#1815)
* track error

* remove useless code

Co-authored-by: huanghuan.27@bytedance.com <huanghuan.27@bytedance.com>
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-12 07:17:56 +01:00
Asim Aslam 630ceb5dad Update README.md (#1820) 2020-07-11 22:18:53 +01:00
Asim Aslam 85ae232936 Add model to service (#1819) 2020-07-11 21:15:59 +01:00
ben-toogood 13ea0eec02 :registry/mdns: fix nil pointer bug (#1818) 2020-07-10 17:47:07 +01:00
ben-toogood 09ec20fded runtime: provide credentials to services (#1817)
* runtime: inject credentials into service

* util/auth: self generate accounts (needed for jwt)

* runtime/kubernetes: add logging for creds

* runtime/kubernetes: serialize secret name

* runtime/kubernetes: remove unused code

* runtime/kubernetes: base64 encode secret

* runtime/kubernetes: remove metadata from secret

* util/kubernetes/client: omit empty secret metadata

* util/kubernetes/client: fix secret template

* util/kubernetes/client: fix secrets

* web: update auth util

* util/auth: fix missing arg

* extend token expiry

* extend token expiry
2020-07-10 16:25:46 +01:00
ben-toogood 3480e0a64e runtime/kubernetes: remove reference to runtime cells (#1814) 2020-07-09 20:21:34 +01:00
ben-toogood 318a80f824 config/cmd: improve cert loading for infra (#1813)
* config/cmd: improve cert loading for infra

* config/cmd: remove certificate_authorities flag

* config/cmd: fix caps

* config/cmd: fix bug with IsSet

* config/cmd: fix bool flags
2020-07-09 18:02:24 +01:00
ben-toogood 6d9a38a747 kubernetes: fixes for production (#1812)
* util/kubernetes/client: add secrets to deployments

* util/kubernetes/client: remove ServiceAccountName override

* debugging

* runtime/kubernetes: fix error

* runtime/kubernetes: remove test secret

* util/kubernetes/client: update default image

* util/kubernetes/client: remove default command for deployments

* runtime/kubernetes: pass source as arg

* runtime/kubernetes: remove debugging

* util/kubernetes/client: revert default image change
2020-07-09 16:29:01 +01:00
Asim Aslam 58d6726380 The start of mud - the micro data model (#1811)
* The start of mud - the micro data model

* add comments
2020-07-09 12:11:32 +01:00
Huan Huang e5db6ea8a7 close r/w (#1810)
Co-authored-by: huanghuan.27@bytedance.com <huanghuan.27@bytedance.com>
2020-07-09 10:28:20 +01:00
Huan Huang 3468331506 feat: refactor register func (#1807)
Co-authored-by: huanghuan.27@bytedance.com <huanghuan.27@bytedance.com>
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-08 18:38:01 +01:00
王旭 1bac08cc0e server.Init(Wait(nil)) update server options context (#1804)
Co-authored-by: wangxu <wangxu@oneniceapp.com>
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-08 18:36:07 +01:00
ben-toogood 333320dcb8 config/cmd: secure broker (#1806)
* config/cmd: secure broker

* config/cmd: remove testing
2020-07-08 16:22:48 +01:00
Asim Aslam ce12c040fa Model updates 2020-07-08 15:09:18 +01:00
Asim Aslam ee36e26edc Add model interface (#1808) 2020-07-08 14:57:45 +01:00
Asim Aslam 3ffb899951 Fix cache options 2020-07-08 14:53:38 +01:00
Asim Aslam 00bd2bc65f cache interface (#1805) 2020-07-08 11:08:59 +01:00
ben-toogood 86f4235aaf config/cmd: custom certificate authorities & secure registry (#1803)
* config/cmd: add registry_secure option

* config/cmd: tmp load ca

* config/cmd: tmp load ca

* config/cmd: refactor certificate_authorities setup

* config/cmd: improve usage
2020-07-08 08:50:08 +01:00
Di Wu b37f9c94b8 Fix invalid register check err in log (#1801)
* remove global error tracking

* rpc_server: fix invalid register err

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-07 14:14:23 +01:00
Asim Aslam 0ed1c70d29 Update README.md 2020-07-07 13:34:42 +01:00
Asim Aslam db8e10834b Update README.md (#1802) 2020-07-07 13:33:54 +01:00
ben-toogood 0a937745cd auth: pass namespace options in auth service requests (#1800)
* auth: pass namespace options in auth service requests

* auth/service/proto: update field index

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-07 08:30:25 +01:00
ben-toogood f5ed7e5833 config/source/service: change default namespace to micro (#1798)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-07 08:19:52 +01:00
Metauro 859b9e7786 feat(errors): add gateway, service error (#1797) 2020-07-06 20:14:59 +01:00
Lars Lehtonen 2b033b6495 store/cockroach: fix dropped errors (#1796)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-06 20:14:36 +01:00
Di Wu 51caf2a24e remove global error tracking (#1777)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-06 20:14:22 +01:00
Enix Yu eaa46c2de7 Add recover in grpc service handler (#1727)
* 🐛 bug(grpc): add missing recover in grpc unkonwn service handler

* Add grpc handler recover testcases

* improve test case

Co-authored-by: ben-toogood <bentoogood@gmail.com>
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-07-06 20:13:51 +01:00
Colin Hoglund 90dca65f55 make environment variable interpolation preprocessor optional (#1715) 2020-07-06 20:13:35 +01:00
Vasiliy Tolstov 97ae2979ad pass request context from request rpc endpoints (#1799)
http middleware can add additional metadata to context,
for example tracing wrappers, pass down it to underlining
services

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-07-06 13:55:17 +01:00
Asim Aslam 6f309dada3 fix typo (#1789) 2020-07-06 13:52:42 +01:00
Huan Huang f99b436ec2 feat: fix register bug (#1791) 2020-07-03 14:38:25 +01:00
ben-toogood c817f29d6e router/registry: fix registry not found bug (#1794) 2020-07-03 13:35:59 +01:00
ben-toogood f744c6248f runtime/service: pass namespace option to service (#1792) 2020-07-03 11:37:14 +01:00
ben-toogood 4ff114e798 router/registry: fix rlock bug when registry errors (#1788)
* client: add select options

* router/registry: fix rlock bug when registry errors

* Revert "client: add select options"

This reverts commit 4d5283452e183f7387b604b51bde1deaf87ee391.

* router/registry: findRoutes under rlock

* add test

Co-authored-by: Dominic Wong <dom@micro.mu>
2020-07-02 18:29:11 +01:00
ben-toogood c58ac35dfc broker/service: use wrapped micro client to authenticate requests (#1782) 2020-07-02 17:54:53 +01:00
ben-toogood b5314829fa client/{grpc,rpc}: fix previous breaking change with CallFunc (router.Route vs registry.Node) (#1781) 2020-07-02 17:26:45 +01:00
ben-toogood 41c7688697 options: add router option (#1783)
* broker/service: use wrapped micro client to authenticate requests

* options: add router option

* Revert "broker/service: use wrapped micro client to authenticate requests"

This reverts commit f2383f37c86467b4ce534313a7f59993a904a712.
2020-07-02 17:17:07 +01:00
ben-toogood b021546c09 client: add select options (#1786) 2020-07-02 17:03:08 +01:00
ben-toogood 6898a65508 selector: add filters to replace depricated client/selector filters (#1785) 2020-07-02 16:09:48 +01:00
ben-toogood d577dd6abe config: add namespace support (#1780)
* config/cmd: use service namespace for config

* config/service: add namespace option to list
2020-07-02 14:07:58 +01:00
Janos Dobronszki 3c633e3577 Sparse checkout of repos, all repo downloads happen to different folders for concurrency support (#1779) 2020-07-02 14:40:40 +02:00
ben-toogood 174e44b846 Deprecate client/selector (#1767)
* client/{grpc,rpc}: depricate selector (wip)

* {client,cmd}: remove client/selector

* deprecate client/selector

* router/static: fix lookup

* config/cmd: add support for legacy static selector flag

* config/cmd: add support for legacy dns selector flag
2020-07-01 17:06:59 +01:00
Dominic Wong a63480a81a router/registry: fix fallback fails if service has been seen already (#1776) 2020-07-01 15:20:30 +01:00
ben-toogood 6d9d94b105 api/router/registry: use resolved domain (#1774) 2020-07-01 12:23:55 +01:00
ben-toogood 64e9185386 router/registry: fix nil eventChan bug (#1773)
* router/registry: fix nil eventChan bug
2020-07-01 12:03:13 +01:00
ben-toogood 1b5c83f3cc registry/mdns: fix deregister bug (#1771)
* registry/mdns: fix deregister bug

* Update registry/mdns_registry.go

Co-authored-by: Dominic Wong <domwongemail@googlemail.com>
2020-07-01 11:09:20 +01:00
ben-toogood 979af853b9 proxy/mucp: use Micro-Namespace to filter networks (#1772) 2020-07-01 10:55:46 +01:00
ben-toogood a64078b5c3 router/service: handle not found error (#1770) 2020-07-01 10:18:19 +01:00
Dominic Wong 58845d7012 only prewarm the route table if requested (#1765) 2020-07-01 10:05:21 +01:00
ben-toogood dcf01ebbf0 router/registry: fix concurrent map iteration and map write (#1762) 2020-06-30 17:40:38 +01:00
Dominic Wong 355ad2a1af push tags to docker hub (#1766) 2020-06-30 17:26:59 +01:00
ben-toogood b882ff3df9 selector: update selector.Select to accept a slice of structs (#1764) 2020-06-30 15:51:26 +01:00
ben-toogood 6337c92cd0 selector: new selector interface with random & roundrobin implementation (#1761)
* selector: implement new selector interface plus random & roundrobin implementations

* selector/roundrobin: remove unused consts

* router: add close method to interface

* selector/roundrobin: fix concurrent map iteration and map write

* selector: replace variadic argument on Select
2020-06-30 14:54:38 +01:00
ben-toogood a95accad56 router: add metadata to routes (#1763)
* router: add metadata to route

* router/registry: set node metadata in route

* router/service: pass metadata to/from router service
2020-06-30 14:10:13 +01:00
Dominic Wong 6532b6208b MDNS registry fix for users on VPNs (#1759)
* filter out unsolicited responses
* send to local ip in case
* allow ip func to be passed in. add option for sending to 0.0.0.0
2020-06-30 11:12:52 +01:00
ben-toogood 0f5c53b6e4 selector: use custom domain (#1760)
* util/wrapper: improve auth errors

* client: add network call option

* client/selector: add domain select option

* client/grpc: pass network option to selector
2020-06-30 10:07:52 +01:00
ben-toogood deea8fecf4 router/registry: add fallback if routes aren't found in the cache (#1758)
* router/registry: add fallback if routes aren't found in the cache

* router: fix rlock bug

* router/registry: pass fetchRoutes into the table, not the router
2020-06-30 09:53:49 +01:00
ben-toogood df3e5364ca api/resolver: add resolve options (#1756)
* api/resolver: Resolve options

* router/registry: fix init bug

* router/registry: fix wildcard query bug

* web: fix registation domain bug

* registry/etcd: pass domain in service metadata

* api/resolver/subdomain: expose domain func

* Update api/resolver/subdomain/subdomain.go

Co-authored-by: Dominic Wong <domwongemail@googlemail.com>

Co-authored-by: Dominic Wong <domwongemail@googlemail.com>
2020-06-29 16:37:45 +01:00
Di Wu 132c1e35fe Fix invalid usage for sync.WaitGroup (#1752)
* Custom private blocks

* Fix invalid usage for sync.WaitGroup

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-06-27 20:07:04 +01:00
Dominic Wong 5967a68e78 cached file store (#1739)
* cached file store
2020-06-26 16:13:53 +01:00
ben-toogood 104b7d8f8d api/resolver: update resolver to enable subdomain routing (#1747)
* api/resolver: update domain / service prefix usage

* api/resolver/subdomain: implement subdomain resolver for domain resolution

* api/handler: fix tests
2020-06-26 14:28:18 +01:00
ben-toogood 4f0f4326df router: improve router configuration (#1745)
* router: update default address to :8084

* service: add router to service options

* config/cmd: improve router setup
2020-06-26 10:38:11 +01:00
ben-toogood ee02511658 proxy/mucp: add support for multi-tenancy (#1746) 2020-06-26 10:31:06 +01:00
ben-toogood a8fc5590a8 client/selector: query across multiple domains (#1725)
* client/selector: query across multiple domains

* client/selector: check for nil services

* config/cmd: fix merge bug
2020-06-25 15:40:23 +01:00
ben-toogood bc60f23ff6 config/cmd: setup registry before router (#1743) 2020-06-25 15:24:31 +01:00
ben-toogood 2000da6fd8 router/registry: add support for registry domains (#1744) 2020-06-25 12:35:00 +01:00
ben-toogood 5ab475636a server/{grpc,rpc}: fix deregister domain bug (#1742)
* server/{grpc,rpc}: fix deregister domain bug

* server/grpc: remove unnecessary slice
2020-06-25 11:25:43 +01:00
ben-toogood 51b4ab0abc registry/memory: watcher bug fixes (#1740)
* registry/memory: watcher bugfixes

* registry/memory: fix nil watcher bug

* registry/memory: fix watcher test
2020-06-25 11:02:35 +01:00
ben-toogood 687a5e2e58 util/wrapper: fix 401 error when requesting the default namespace (#1741) 2020-06-25 10:19:03 +01:00
Dominic Wong fcd307d902 Build and test forked PRs properly (#1738)
* build forks on PRs properly
2020-06-24 23:41:27 +01:00
ben-toogood 00cd07a3a6 util/wrapper: set auth credentials on streams (#1735) 2020-06-24 16:45:34 +01:00
Dominic Wong a2a1f4dfbd support mono repo deps (#1736)
* support mono repo deps

* add protoc
2020-06-24 16:27:22 +01:00
ben-toogood 2b506b1a2a auth/service: use address option since router may not be configured (#1734) 2020-06-24 13:47:43 +01:00
ben-toogood a2550820d3 router: add to service options; add dns and static implementations (#1733)
* config/cmd: add router to service options

* router/service: use micro client
2020-06-24 11:46:51 +01:00
ben-toogood c940961574 router: update interface (#1732)
* router: replace Start and Stop with Close

* router: update default network to micro

* router: update tests
2020-06-24 11:09:16 +01:00
Dominic Wong 695cc9d526 Build against micro and examples on pull requests (#1724)
* Build against micro and examples on pull requests
2020-06-19 17:19:58 +01:00
ben-toogood 87543b2c8a registry/etcd: add support for domain options (#1714) 2020-06-19 14:58:16 +01:00
ben-toogood 5f9c3a6efd registry/cache: add support for the domain option (#1722) 2020-06-19 13:16:44 +01:00
ben-toogood 2b889087bd config/cmd: fix selector setup bug (#1723) 2020-06-19 13:11:48 +01:00
ben-toogood ece02a6d21 util/wrapper: fix noop auth nil account bug (#1721)
* util/wrapper: fix noop nil account

* util/wrapper: improve comments

* util/wrapper: update tests
2020-06-19 12:16:39 +01:00
ben-toogood 58c6bbbf6b registry/service: pass domain options via rpc (#1719)
* registry/service: regenerate proto

* registry/service: pass domain in proto request options

* registry/service: stop defaulting metadata

* registry: add default domain const; remove from implementations

* registry/memory: fix typo
2020-06-19 10:34:12 +01:00
ben-toogood c16f4b741c server: register in the services namespace (#1718) 2020-06-19 09:24:32 +01:00
Colin Hoglund 83cecdb294 config: use configured reader by default (#1717) 2020-06-19 08:49:30 +01:00
ben-toogood 8c7c27c573 registry/memory: add support for domain options (#1713)
* registry/memory: add support for the domain options

* registry/memory: swap Fatal test cases with Error

* registry/memory: fix wildcard not found bug

* registry/memory: replace locks with rlocks

* registry/memory: fix deregistration bug
2020-06-18 12:39:19 +01:00
ben-toogood 5fd36d6cc0 config/cmd: remove package duplicate initialization (#1711) 2020-06-18 10:14:04 +01:00
ben-toogood 3b40fde68b registry/mdns: add domain support (#1708)
* registry: add domain options

* registry/mdns: implement domain options

* registry/mdns: return node domain in metadata when querying using wildcard

* Fix nil pointer exception

* registry/mdns: return error from deregister

* registy/mdns: rename tld => domain
2020-06-17 13:23:41 +01:00
ben-toogood 9d3365c4be auth: rename auth.Namespace to auth.Issuer (#1710) 2020-06-17 12:26:27 +01:00
sunfuze 2efb459c66 Fix config watch (#1670)
* add dirty overrite test case

* need version to figure out if config need update or not

* using nanosecond as version for two goroutine can run in same second

* config should check snapshot version when update

* set checksum of ChangeSet

Co-authored-by: Asim Aslam <asim@aslam.me>
2020-06-16 17:10:52 +01:00
Di Wu 6add74b4f6 Custom private blocks (#1705)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-06-16 17:05:42 +01:00
Dominic Wong c67d78f1ef update PR template, not using gitflow anymore 2020-06-16 17:00:15 +01:00
Dominic Wong a89610ffea Merge pull request #1706 from micro/develop
Develop -> Master. Abandoning gitflow model
2020-06-16 16:57:40 +01:00
Dominic Wong da9bb11240 Merge branch 'master' into develop 2020-06-16 16:52:24 +01:00
Dmitry Kozlov a3a1a84172 Split long discord output message into the chunks by 2000 characters (#1704)
Signed-off-by: Dmitry Kozlov <dmitry.f.kozlov@gmail.com>
2020-06-15 22:22:00 +01:00
ben-toogood 1179d7e89a registry/mdns: fix nil host bug (#1703) 2020-06-15 16:13:45 +01:00
Asim Aslam a5df913926 Update README.md (#1695) 2020-06-12 15:07:19 +01:00
Asim Aslam 9ce706191b Update FUNDING.yml (#1692) 2020-06-12 15:07:19 +01:00
Dominic Wong 73c2f25935 Fix regex detection. Fixes #1663 (#1696) 2020-06-10 11:18:03 +01:00
Dominic Wong 8e81cea96f Fix race condition when updating process being waited on (#1694) 2020-06-10 09:36:41 +01:00
Janos Dobronszki cdd8f9fd82 Initialize selector before we make an auth.Generate call (#1693) 2020-06-09 12:47:31 +02:00
Dominic Wong a346064eaf Fix race when opening DB for first time (#1691) 2020-06-08 16:19:22 +01:00
Dominic Wong 47bdd5c993 Ignore "no such process" error (#1686)
* Cleanup how status is updated for service. Ignore "no such process" error as it could be that the pid died

* add nice error log to record process error exit
2020-06-08 10:47:25 +01:00
Janos Dobronszki 9af12ff9df Sigterm instead of Sigkill (#1687)
Co-authored-by: Dominic Wong <domwongemail@googlemail.com>
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-06-06 14:04:14 +01:00
Vasiliy Tolstov 6c7bcf3883 api/handler/rpc: dont log error on normal websocket error code (#1688)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-06-06 00:03:43 +03:00
ben-toogood bbc3b7040b util/wrapper: Add Static Client wrapper (#1685)
* util/wrapper: Add Static Client wrapper

* util/wrapper/static: pass address to stream too

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* add static client wrapper tests

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* server: fix error message spaces between words

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* server/{rpc,grpc}: replace log.Error with log.Errorf

* server/grpc: fix log typo

* server/rpc: fix log typo

Co-authored-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-06-05 10:18:35 +01:00
Dominic Wong 582f2e8b94 Add build and test of micro to pre-release testing (#1684)
* fix up example test build

* build and test micro when cutting a new release
2020-06-04 13:46:22 +01:00
Dominic Wong bd3ef67328 Build all micro/examples for release-X.X.X branches (#1683)
* Build all the examples on push to any release branch
2020-06-04 09:32:17 +01:00
Dominic Wong 1ccd4cd940 Merge branch 'master' into develop 2020-06-03 10:41:39 +01:00
ben-toogood 003731ace9 Fix incorrect namespace variable name (merge conflict) (#1677) 2020-06-03 09:48:19 +01:00
ben-toogood b6f3e8b715 Move auth account creation to config/cmd (#1676) 2020-06-03 09:43:20 +01:00
ben-toogood 8f6ec21b91 Apply wrappers to gRPC streams (#1675)
* Add wrappers to grpc streams

* Fix typo
2020-06-02 17:56:26 +01:00
1288 changed files with 23047 additions and 160106 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
# These are supported funding model platforms
github: asim
github: micro
+4 -5
View File
@@ -1,10 +1,9 @@
## Pull Request template
Please, go through these steps before clicking submit on this PR.
1. Make sure this PR targets the `develop` branch. We follow the git-flow branching model.
2. Give a descriptive title to your PR.
3. Provide a description of your changes.
4. Make sure you have some relevant tests.
5. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable).
1. Give a descriptive title to your PR.
2. Provide a description of your changes.
3. Make sure you have some relevant tests.
4. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable).
**PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING**
+22
View File
@@ -0,0 +1,22 @@
name: Docker
on:
push:
branches:
- master
tags:
- v2.*
- v3.*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
name: Check out repository
- uses: elgohr/Publish-Docker-Github-Action@2.12
name: Build and Push Docker Image
with:
name: micro/go-micro
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tag_names: true
+6 -3
View File
@@ -8,10 +8,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.15
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.13
id: go
- name: Check out code into the Go module directory
@@ -25,4 +25,7 @@ jobs:
id: tests
env:
IN_TRAVIS_CI: yes
run: go test -v ./...
run: |
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.1.4.linux-amd64.tgz | tar xvz
cockroach-v20.1.4.linux-amd64/cockroach start-single-node --insecure &
go test -v ./...
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# set -x
function build_binary {
echo building $1
pushd $1
go build -o _main
local ret=$?
if [ $ret -gt 0 ]; then
failed=1
failed_arr+=($1)
fi
popd
}
function is_main {
grep "package main" -l -dskip $1/*.go > /dev/null 2>&1
}
function check_dir {
is_main $1
local ret=$?
if [ $ret == 0 ]; then
build_binary $1 $2
fi
for filename in $1/*; do
if [ -d $filename ]; then
check_dir $filename $2
fi
done
}
failed_arr=()
failed=0
go mod edit -replace github.com/micro/go-micro/v2=github.com/$2/v2@$1
check_dir . $1
if [ $failed -gt 0 ]; then
echo Some builds failed
printf '%s\n' "${failed_arr[@]}"
fi
exit $failed
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# set -x
failed=0
go mod edit -replace github.com/micro/go-micro/v2=github.com/$2/v2@$1
# basic test, build the binary
go install
failed=$?
if [ $failed -gt 0 ]; then
exit $failed
fi
# unit tests
IN_TRAVIS_CI=yes go test -v ./...
./scripts/test-docker.sh
# Generate keys for JWT tests
ssh-keygen -f /tmp/sshkey -m pkcs8 -q -N ""
ssh-keygen -f /tmp/sshkey -e -m pkcs8 > /tmp/sshkey.pub
go clean -testcache && IN_TRAVIS_CI=yes go test --tags=integration -v ./test
+40 -4
View File
@@ -7,13 +7,17 @@ jobs:
name: Test repo
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.15
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.13
id: go
- name: Setup Kind
uses: engineerd/setup-kind@v0.4.0
with:
version: v0.8.1
- name: Check out code into the Go module directory
uses: actions/checkout@v2
@@ -25,5 +29,37 @@ jobs:
id: tests
env:
IN_TRAVIS_CI: yes
run: go test -v ./...
run: |
kubectl apply -f runtime/kubernetes/test/test.yaml
sudo mkdir -p /var/run/secrets/kubernetes.io/serviceaccount
sudo chmod 777 /var/run/secrets/kubernetes.io/serviceaccount
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.1.4.linux-amd64.tgz | tar -xvz
cockroach-v20.1.4.linux-amd64/cockroach start-single-node --insecure &
wget -q https://github.com/nats-io/nats-streaming-server/releases/download/v0.18.0/nats-streaming-server-v0.18.0-linux-amd64.zip
unzip ./nats-streaming-server-v0.18.0-linux-amd64.zip
export PATH=$PATH:./nats-streaming-server-v0.18.0-linux-amd64
nats-streaming-server &
go test -tags kubernetes,nats -v ./...
- name: Notify of test failure
if: failure()
uses: rtCamp/action-slack-notify@v2.0.0
env:
SLACK_CHANNEL: build
SLACK_COLOR: '#BF280A'
SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
SLACK_TITLE: Tests Failed
SLACK_USERNAME: GitHub Actions
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
- name: Notify of test success
if: success()
uses: rtCamp/action-slack-notify@v2.0.0
env:
SLACK_CHANNEL: build
SLACK_COLOR: '#1FAD2B'
SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
SLACK_TITLE: Tests Passed
SLACK_USERNAME: GitHub Actions
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
+1
View File
@@ -0,0 +1 @@
go-micro.dev
+13
View File
@@ -0,0 +1,13 @@
FROM golang:1.13-alpine
RUN mkdir /user && \
echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \
echo 'nobody:x:65534:' > /user/group
ENV GO111MODULE=on
RUN apk --no-cache add make git gcc libtool musl-dev ca-certificates dumb-init && \
rm -rf /var/cache/apk/* /tmp/*
WORKDIR /
COPY ./go.mod ./go.sum ./
RUN go mod download && rm go.mod go.sum
+7 -43
View File
@@ -1,6 +1,6 @@
# Go Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Go.Dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/asim/go-micro/v3?tab=doc)
# Go Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Go.Dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/micro/go-micro/v3?tab=overview) [![Travis CI](https://api.travis-ci.org/micro/go-micro.svg?branch=master)](https://travis-ci.org/micro/go-micro) [![Go Report Card](https://goreportcard.com/badge/micro/go-micro)](https://goreportcard.com/report/github.com/micro/go-micro)
Go Micro is a framework for distributed systems development.
Go Micro is a standard library for microservices.
## Overview
@@ -34,8 +34,7 @@ across the services and retry a different node if there's a problem.
to seamlessly encode and decode Go types for you. Any variety of messages could be encoded and sent from different clients. The client
and server handle this by default. This includes protobuf and json by default.
- **RPC Client/Server** - RPC based request/response with support for bidirectional streaming. We provide an abstraction for synchronous
communication. A request made to a service will be automatically resolved, load balanced, dialled and streamed.
- **gRPC Transport** - gRPC based request/response with support for bidirectional streaming. We provide an abstraction for synchronous communication. A request made to a service will be automatically resolved, load balanced, dialled and streamed.
- **Async Messaging** - PubSub is built in as a first class citizen for asynchronous communication and event driven architectures.
Event notifications are a core pattern in micro service development. The default messaging system is a HTTP event message broker.
@@ -44,49 +43,14 @@ Event notifications are a core pattern in micro service development. The default
leadership are built in as a Sync interface. When using an eventually consistent database or scheduling use the Sync interface.
- **Pluggable Interfaces** - Go Micro makes use of Go interfaces for each distributed system abstraction. Because of this these interfaces
are pluggable and allows Go Micro to be runtime agnostic. You can plugin any underlying technology.
are pluggable and allows Go Micro to be runtime agnostic. You can plugin any underlying technology. Find plugins in
[github.com/micro/go-plugins](https://github.com/micro/go-plugins).
## Getting Started
To make use of Go Micro
```golang
import "github.com/asim/go-micro/v3"
// create a new service
service := micro.NewService(
micro.Name("helloworld"),
)
// initialise flags
service.Init()
// start the service
service.Run()
```
See the [examples](https://github.com/micro/go-micro/tree/master/examples) for detailed information on usage.
## Command Line Interface
See [cmd/gomu](https://github.com/asim/go-micro/tree/master/cmd/gomu) for the command line interface.
## Code Generation
See [cmd/protoc-gen-micro](https://github.com/micro/go-micro/tree/master/cmd/protoc-gen-micro) for protobuf code generation.
## Example Usage
See [examples](https://github.com/micro/go-micro/tree/master/examples) directory for usage examples.
## Plugins
See [plugins](https://github.com/micro/go-micro/tree/master/plugins) directory for all the plugins.
## Services
See [services](https://github.com/micro/go-micro/tree/master/services) directory for third party services.
See [pkg.go.dev](https://pkg.go.dev/github.com/micro/go-micro/v3?tab=overview) for usage.
## License
Go Micro is Apache 2.0 licensed.
+3 -3
View File
@@ -5,8 +5,8 @@ import (
"regexp"
"strings"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/server"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/server"
)
type Api interface {
@@ -18,7 +18,7 @@ type Api interface {
Register(*Endpoint) error
// Register a route
Deregister(*Endpoint) error
// Implemenation of api
// Implementation of api
String() string
}
+8 -10
View File
@@ -4,13 +4,13 @@ package api
import (
"net/http"
goapi "github.com/asim/go-micro/v3/api"
"github.com/asim/go-micro/v3/api/handler"
api "github.com/asim/go-micro/v3/api/proto"
"github.com/asim/go-micro/v3/client"
"github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/selector"
"github.com/asim/go-micro/v3/util/ctx"
goapi "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/api/handler"
api "github.com/micro/go-micro/v3/api/proto"
"github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/util/ctx"
"github.com/micro/go-micro/v3/util/router"
)
type apiHandler struct {
@@ -71,10 +71,8 @@ func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// create the context from headers
cx := ctx.FromRequest(r)
// create strategy
so := selector.WithStrategy(strategy(service.Services))
if err := c.Call(cx, req, rsp, client.WithSelectOption(so)); err != nil {
if err := c.Call(cx, req, rsp, client.WithRouter(router.New(service.Services))); err != nil {
w.Header().Set("Content-Type", "application/json")
ce := errors.Parse(err.Error())
switch ce.Code {
+1 -11
View File
@@ -7,9 +7,7 @@ import (
"net/http"
"strings"
api "github.com/asim/go-micro/v3/api/proto"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/selector"
api "github.com/micro/go-micro/v3/api/proto"
"github.com/oxtoacart/bpool"
)
@@ -109,11 +107,3 @@ func requestToProto(r *http.Request) (*api.Request, error) {
return req, nil
}
// strategy is a hack for selection
func strategy(services []*registry.Service) selector.Strategy {
return func(_ []*registry.Service) selector.Next {
// ignore input to this function, use services above
return selector.Random(services)
}
}
+3 -3
View File
@@ -10,10 +10,10 @@ import (
"strings"
"time"
"github.com/asim/go-micro/v3/api/handler"
proto "github.com/asim/go-micro/v3/api/proto"
"github.com/asim/go-micro/v3/util/ctx"
"github.com/google/uuid"
"github.com/micro/go-micro/v3/api/handler"
proto "github.com/micro/go-micro/v3/api/proto"
"github.com/micro/go-micro/v3/util/ctx"
"github.com/oxtoacart/bpool"
)
+16 -12
View File
@@ -4,13 +4,14 @@ package http
import (
"errors"
"fmt"
"math/rand"
"net/http"
"net/http/httputil"
"net/url"
"github.com/asim/go-micro/v3/api"
"github.com/asim/go-micro/v3/api/handler"
"github.com/asim/go-micro/v3/selector"
"github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v3/registry"
)
const (
@@ -20,7 +21,7 @@ const (
type httpHandler struct {
options handler.Options
// set with different initialiser
// set with different initializer
s *api.Service
}
@@ -64,16 +65,19 @@ func (h *httpHandler) getService(r *http.Request) (string, error) {
return "", errors.New("no route found")
}
// create a random selector
next := selector.Random(service.Services)
// get the next node
s, err := next()
if err != nil {
return "", nil
// get the nodes for this service
var nodes []*registry.Node
for _, srv := range service.Services {
nodes = append(nodes, srv.Nodes...)
}
return fmt.Sprintf("http://%s", s.Address), nil
// select a random node
if len(nodes) == 0 {
return "", errors.New("no route found")
}
node := nodes[rand.Int()%len(nodes)]
return fmt.Sprintf("http://%s", node.Address), nil
}
func (h *httpHandler) String() string {
+9 -8
View File
@@ -6,16 +6,17 @@ import (
"net/http/httptest"
"testing"
"github.com/asim/go-micro/v3/api/handler"
"github.com/asim/go-micro/v3/api/resolver"
"github.com/asim/go-micro/v3/api/resolver/vpath"
"github.com/asim/go-micro/v3/api/router"
regRouter "github.com/asim/go-micro/v3/api/router/registry"
"github.com/asim/go-micro/v3/registry"
"github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/api/resolver/vpath"
"github.com/micro/go-micro/v3/api/router"
regRouter "github.com/micro/go-micro/v3/api/router/registry"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/memory"
)
func testHttp(t *testing.T, path, service, ns string) {
r := registry.NewMemoryRegistry()
r := memory.NewRegistry()
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
@@ -57,7 +58,7 @@ func testHttp(t *testing.T, path, service, ns string) {
router.WithHandler("http"),
router.WithRegistry(r),
router.WithResolver(vpath.NewResolver(
resolver.WithNamespace(resolver.StaticNamespace(ns)),
resolver.WithServicePrefix(ns),
)),
)
+5 -4
View File
@@ -1,8 +1,9 @@
package handler
import (
"github.com/asim/go-micro/v3/api/router"
"github.com/asim/go-micro/v3/client"
"github.com/micro/go-micro/v3/api/router"
"github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/client/grpc"
)
var (
@@ -26,7 +27,7 @@ func NewOptions(opts ...Option) Options {
}
if options.Client == nil {
WithClient(client.NewClient())(&options)
WithClient(grpc.NewClient())(&options)
}
// set namespace if blank
@@ -61,7 +62,7 @@ func WithClient(c client.Client) Option {
}
}
// WithmaxRecvSize specifies max body size
// WithMaxRecvSize specifies max body size
func WithMaxRecvSize(size int64) Option {
return func(o *Options) {
o.MaxRecvSize = size
+19 -48
View File
@@ -5,25 +5,23 @@ import (
"encoding/json"
"io"
"net/http"
"net/textproto"
"strconv"
"strings"
"github.com/asim/go-micro/v3/api"
"github.com/asim/go-micro/v3/api/handler"
"github.com/asim/go-micro/v3/api/internal/proto"
"github.com/asim/go-micro/v3/client"
"github.com/asim/go-micro/v3/codec"
"github.com/asim/go-micro/v3/codec/jsonrpc"
"github.com/asim/go-micro/v3/codec/protorpc"
"github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/logger"
"github.com/asim/go-micro/v3/metadata"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/selector"
"github.com/asim/go-micro/v3/util/ctx"
"github.com/asim/go-micro/v3/util/qson"
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v3/api/internal/proto"
"github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/codec/jsonrpc"
"github.com/micro/go-micro/v3/codec/protorpc"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v3/util/ctx"
"github.com/micro/go-micro/v3/util/qson"
"github.com/micro/go-micro/v3/util/router"
"github.com/oxtoacart/bpool"
)
@@ -65,14 +63,6 @@ func (b *buffer) Write(_ []byte) (int, error) {
return 0, nil
}
// strategy is a hack for selection
func strategy(services []*registry.Service) selector.Strategy {
return func(_ []*registry.Service) selector.Next {
// ignore input to this function, use services above
return selector.Random(services)
}
}
func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
bsize := handler.DefaultMaxRecvSize
if h.opts.MaxRecvSize > 0 {
@@ -113,36 +103,17 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// create context
cx := ctx.FromRequest(r)
// get context from http handler wrappers
md, ok := metadata.FromContext(r.Context())
if !ok {
md = make(metadata.Metadata)
}
// fill contex with http headers
md["Host"] = r.Host
md["Method"] = r.Method
// get canonical headers
for k := range r.Header {
// may be need to get all values for key like r.Header.Values() provide in go 1.14
md[textproto.CanonicalMIMEHeaderKey(k)] = r.Header.Get(k)
}
// merge context with overwrite
cx = metadata.MergeContext(cx, md, true)
// set merged context to request
*r = *r.Clone(cx)
// if stream we currently only support json
if isStream(r, service) {
// drop older context as it can have timeouts and create new
// md, _ := metadata.FromContext(cx)
//serveWebsocket(context.TODO(), w, r, service, c)
serveWebsocket(cx, w, r, service, c)
return
}
// create strategy
so := selector.WithStrategy(strategy(service.Services))
// create custom router
callOpt := client.WithRouter(router.New(service.Services))
// walk the standard call path
// get payload
@@ -174,7 +145,7 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
)
// make the call
if err := c.Call(cx, req, response, client.WithSelectOption(so)); err != nil {
if err := c.Call(cx, req, response, callOpt); err != nil {
writeError(w, r, err)
return
}
@@ -209,7 +180,7 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
client.WithContentType(ct),
)
// make the call
if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil {
if err := c.Call(cx, req, &response, callOpt); err != nil {
writeError(w, r, err)
return
}
@@ -278,7 +249,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
return nil, err
}
return raw.Marshal()
case strings.Contains(ct, "application/www-x-form-urlencoded"):
case strings.Contains(ct, "application/x-www-form-urlencoded"):
r.ParseForm()
// generate a new set of values from the form
@@ -294,7 +265,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
// otherwise as per usual
ctx := r.Context()
// dont user meadata.FromContext as it mangles names
// dont user metadata.FromContext as it mangles names
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(map[string]string)
+4 -14
View File
@@ -4,11 +4,10 @@ import (
"bytes"
"encoding/json"
"net/http"
"reflect"
"testing"
go_api "github.com/asim/go-micro/v3/api/proto"
"github.com/golang/protobuf/proto"
go_api "github.com/micro/go-micro/v3/api/proto"
)
func TestRequestPayloadFromRequest(t *testing.T) {
@@ -28,12 +27,7 @@ func TestRequestPayloadFromRequest(t *testing.T) {
t.Fatal("Failed to marshal proto to JSON ", err)
}
type jsonUrl struct {
Key1 string `json:"key1"`
Key2 string `json:"key2"`
Name string `json:"name"`
}
jUrl := &jsonUrl{Key1: "val1", Key2: "val2", Name: "Test"}
jsonUrlBytes := []byte(`{"key1":"val1","key2":"val2","name":"Test"}`)
t.Run("extracting a json from a POST request with url params", func(t *testing.T) {
r, err := http.NewRequest("POST", "http://localhost/my/path?key1=val1&key2=val2", bytes.NewReader(jsonBytes))
@@ -45,12 +39,8 @@ func TestRequestPayloadFromRequest(t *testing.T) {
if err != nil {
t.Fatalf("Failed to extract payload from request: %v", err)
}
extJUrl := &jsonUrl{}
if err := json.Unmarshal(extByte, extJUrl); err != nil {
t.Fatalf("Failed to unmarshal payload from request: %v", err)
}
if !reflect.DeepEqual(extJUrl, jUrl) {
t.Fatalf("Expected %v and %v to match", extJUrl, jUrl)
if string(extByte) != string(jsonUrlBytes) {
t.Fatalf("Expected %v and %v to match", string(extByte), jsonUrlBytes)
}
})
+10 -8
View File
@@ -9,14 +9,14 @@ import (
"strings"
"time"
"github.com/asim/go-micro/v3/api"
"github.com/asim/go-micro/v3/client"
raw "github.com/asim/go-micro/v3/codec/bytes"
"github.com/asim/go-micro/v3/logger"
"github.com/asim/go-micro/v3/selector"
"github.com/gobwas/httphead"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/client"
raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v3/util/router"
)
// serveWebsocket will stream rpc back over websockets assuming json
@@ -38,7 +38,7 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
}
hdr := make(http.Header)
if proto, ok := r.Header["Sec-Websocket-Protocol"]; ok {
if proto, ok := r.Header["Sec-WebSocket-Protocol"]; ok {
for _, p := range proto {
switch p {
case "binary":
@@ -110,9 +110,11 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
client.StreamingRequest(),
)
so := selector.WithStrategy(strategy(service.Services))
// create custom router
callOpt := client.WithRouter(router.New(service.Services))
// create a new stream
stream, err := c.Stream(ctx, req, client.WithSelectOption(so))
stream, err := c.Stream(ctx, req, callOpt)
if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Error(err)
+15 -11
View File
@@ -5,15 +5,16 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/asim/go-micro/v3/api"
"github.com/asim/go-micro/v3/api/handler"
"github.com/asim/go-micro/v3/selector"
"github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v3/registry"
)
const (
@@ -70,16 +71,19 @@ func (wh *webHandler) getService(r *http.Request) (string, error) {
return "", errors.New("no route found")
}
// create a random selector
next := selector.Random(service.Services)
// get the next node
s, err := next()
if err != nil {
return "", nil
// get the nodes
var nodes []*registry.Node
for _, srv := range service.Services {
nodes = append(nodes, srv.Nodes...)
}
if len(nodes) == 0 {
return "", errors.New("no route found")
}
return fmt.Sprintf("http://%s", s.Address), nil
// select a random node
node := nodes[rand.Int()%len(nodes)]
return fmt.Sprintf("http://%s", node.Address), nil
}
// serveWebSocket used to serve a web socket proxied connection
+10 -4
View File
@@ -6,12 +6,17 @@ import (
"net/http"
"strings"
"github.com/asim/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/api/resolver"
)
type Resolver struct{}
type Resolver struct {
opts resolver.Options
}
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
// /foo.Bar/Service
if req.URL.Path == "/" {
return nil, errors.New("unknown name")
@@ -26,6 +31,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
@@ -34,5 +40,5 @@ func (r *Resolver) String() string {
}
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{}
return &Resolver{opts: resolver.NewOptions(opts...)}
}
+6 -2
View File
@@ -4,19 +4,23 @@ package host
import (
"net/http"
"github.com/asim/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/api/resolver"
)
type Resolver struct {
opts resolver.Options
}
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
return &resolver.Endpoint{
Name: req.Host,
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
+46 -16
View File
@@ -1,23 +1,16 @@
package resolver
import (
"net/http"
"github.com/micro/go-micro/v3/registry"
)
// NewOptions returns new initialised options
func NewOptions(opts ...Option) Options {
var options Options
for _, o := range opts {
o(&options)
}
if options.Namespace == nil {
options.Namespace = StaticNamespace("go.micro")
}
return options
type Options struct {
Handler string
ServicePrefix string
}
type Option func(o *Options)
// WithHandler sets the handler being used
func WithHandler(h string) Option {
return func(o *Options) {
@@ -25,9 +18,46 @@ func WithHandler(h string) Option {
}
}
// WithNamespace sets the function which determines the namespace for a request
func WithNamespace(n func(*http.Request) string) Option {
// WithServicePrefix sets the ServicePrefix option
func WithServicePrefix(p string) Option {
return func(o *Options) {
o.Namespace = n
o.ServicePrefix = p
}
}
// NewOptions returns new initialised options
func NewOptions(opts ...Option) Options {
var options Options
for _, o := range opts {
o(&options)
}
return options
}
// ResolveOptions are used when resolving a request
type ResolveOptions struct {
Domain string
}
// ResolveOption sets an option
type ResolveOption func(*ResolveOptions)
// Domain sets the resolve Domain option
func Domain(n string) ResolveOption {
return func(o *ResolveOptions) {
o.Domain = n
}
}
// NewResolveOptions returns new initialised resolve options
func NewResolveOptions(opts ...ResolveOption) ResolveOptions {
var options ResolveOptions
for _, o := range opts {
o(&options)
}
if len(options.Domain) == 0 {
options.Domain = registry.DefaultDomain
}
return options
}
+7 -4
View File
@@ -5,26 +5,29 @@ import (
"net/http"
"strings"
"github.com/asim/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/api/resolver"
)
type Resolver struct {
opts resolver.Options
}
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
if req.URL.Path == "/" {
return nil, resolver.ErrNotFound
}
parts := strings.Split(req.URL.Path[1:], "/")
ns := r.opts.Namespace(req)
return &resolver.Endpoint{
Name: ns + "." + parts[0],
Name: r.opts.ServicePrefix + "." + parts[0],
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
+3 -15
View File
@@ -13,7 +13,7 @@ var (
// Resolver resolves requests to endpoints
type Resolver interface {
Resolve(r *http.Request) (*Endpoint, error)
Resolve(r *http.Request, opts ...ResolveOption) (*Endpoint, error)
String() string
}
@@ -27,18 +27,6 @@ type Endpoint struct {
Method string
// HTTP Path e.g /greeter.
Path string
}
type Options struct {
Handler string
Namespace func(*http.Request) string
}
type Option func(o *Options)
// StaticNamespace returns the same namespace for each request
func StaticNamespace(ns string) func(*http.Request) string {
return func(*http.Request) string {
return ns
}
// Domain endpoint exists within
Domain string
}
+85
View File
@@ -0,0 +1,85 @@
// Package subdomain is a resolver which uses the subdomain to determine the domain to route to. It
// offloads the endpoint resolution to a child resolver which is provided in New.
package subdomain
import (
"net"
"net/http"
"strings"
"github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/logger"
"golang.org/x/net/publicsuffix"
)
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
options := resolver.NewOptions(opts...)
return &Resolver{options, parent}
}
type Resolver struct {
opts resolver.Options
resolver.Resolver
}
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
if dom := r.Domain(req); len(dom) > 0 {
opts = append(opts, resolver.Domain(dom))
}
return r.Resolver.Resolve(req, opts...)
}
func (r *Resolver) Domain(req *http.Request) string {
// determine the host, e.g. foobar.m3o.app
host := req.URL.Hostname()
if len(host) == 0 {
if h, _, err := net.SplitHostPort(req.Host); err == nil {
host = h // host does contain a port
} else if strings.Contains(err.Error(), "missing port in address") {
host = req.Host // host does not contain a port
}
}
// check for an ip address
if net.ParseIP(host) != nil {
return ""
}
// check for dev enviroment
if host == "localhost" || host == "127.0.0.1" {
return ""
}
// extract the top level domain plus one (e.g. 'myapp.com')
domain, err := publicsuffix.EffectiveTLDPlusOne(host)
if err != nil {
logger.Debugf("Unable to extract domain from %v", host)
return ""
}
// there was no subdomain
if host == domain {
return ""
}
// remove the domain from the host, leaving the subdomain, e.g. "staging.foo.myapp.com" => "staging.foo"
subdomain := strings.TrimSuffix(host, "."+domain)
// ignore the API subdomain
if subdomain == "api" {
return ""
}
// return the reversed subdomain as the namespace, e.g. "staging.foo" => "foo-staging"
comps := strings.Split(subdomain, ".")
for i := len(comps)/2 - 1; i >= 0; i-- {
opp := len(comps) - 1 - i
comps[i], comps[opp] = comps[opp], comps[i]
}
return strings.Join(comps, "-")
}
func (r *Resolver) String() string {
return "subdomain"
}
+71
View File
@@ -0,0 +1,71 @@
package subdomain
import (
"net/http"
"net/url"
"testing"
"github.com/micro/go-micro/v3/api/resolver/vpath"
"github.com/stretchr/testify/assert"
)
func TestResolve(t *testing.T) {
tt := []struct {
Name string
Host string
Result string
}{
{
Name: "Top level domain",
Host: "micro.mu",
Result: "micro",
},
{
Name: "Effective top level domain",
Host: "micro.com.au",
Result: "micro",
},
{
Name: "Subdomain dev",
Host: "dev.micro.mu",
Result: "dev",
},
{
Name: "Subdomain foo",
Host: "foo.micro.mu",
Result: "foo",
},
{
Name: "Multi-level subdomain",
Host: "staging.myapp.m3o.app",
Result: "myapp-staging",
},
{
Name: "Dev host",
Host: "127.0.0.1",
Result: "micro",
},
{
Name: "Localhost",
Host: "localhost",
Result: "micro",
},
{
Name: "IP host",
Host: "81.151.101.146",
Result: "micro",
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
r := NewResolver(vpath.NewResolver())
result, err := r.Resolve(&http.Request{URL: &url.URL{Host: tc.Host, Path: "foo/bar"}})
assert.Nil(t, err, "Expecter err to be nil")
if result != nil {
assert.Equal(t, tc.Result, result.Domain, "Expected %v but got %v", tc.Result, result.Domain)
}
})
}
}
+16 -10
View File
@@ -7,7 +7,7 @@ import (
"regexp"
"strings"
"github.com/asim/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/api/resolver"
)
func NewResolver(opts ...resolver.Option) resolver.Resolver {
@@ -22,36 +22,41 @@ var (
re = regexp.MustCompile("^v[0-9]+$")
)
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
if req.URL.Path == "/" {
return nil, errors.New("unknown name")
}
options := resolver.NewResolveOptions(opts...)
parts := strings.Split(req.URL.Path[1:], "/")
if len(parts) == 1 {
return &resolver.Endpoint{
Name: r.withNamespace(req, parts...),
Name: r.withPrefix(parts...),
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
// /v1/foo
if re.MatchString(parts[0]) {
return &resolver.Endpoint{
Name: r.withNamespace(req, parts[0:2]...),
Name: r.withPrefix(parts[0:2]...),
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
return &resolver.Endpoint{
Name: r.withNamespace(req, parts[0]),
Name: r.withPrefix(parts[0]),
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
@@ -59,11 +64,12 @@ func (r *Resolver) String() string {
return "path"
}
func (r *Resolver) withNamespace(req *http.Request, parts ...string) string {
ns := r.opts.Namespace(req)
if len(ns) == 0 {
return strings.Join(parts, ".")
// withPrefix transforms "foo" into "go.micro.api.foo"
func (r *Resolver) withPrefix(parts ...string) string {
p := r.opts.ServicePrefix
if len(p) > 0 {
parts = append([]string{p}, parts...)
}
return strings.Join(append([]string{ns}, parts...), ".")
return strings.Join(parts, ".")
}
+5 -4
View File
@@ -1,9 +1,10 @@
package router
import (
"github.com/asim/go-micro/v3/api/resolver"
"github.com/asim/go-micro/v3/api/resolver/vpath"
"github.com/asim/go-micro/v3/registry"
"github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/api/resolver/vpath"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/mdns"
)
type Options struct {
@@ -17,7 +18,7 @@ type Option func(o *Options)
func NewOptions(opts ...Option) Options {
options := Options{
Handler: "meta",
Registry: registry.DefaultRegistry,
Registry: mdns.NewRegistry(),
}
for _, o := range opts {
+14 -11
View File
@@ -10,13 +10,13 @@ import (
"sync"
"time"
"github.com/asim/go-micro/v3/api"
"github.com/asim/go-micro/v3/api/router"
"github.com/asim/go-micro/v3/api/router/util"
"github.com/asim/go-micro/v3/logger"
"github.com/asim/go-micro/v3/metadata"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/registry/cache"
"github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/api/router"
"github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/cache"
util "github.com/micro/go-micro/v3/util/router"
)
// endpoint struct, that holds compiled pcre
@@ -99,7 +99,7 @@ func (r *registryRouter) process(res *registry.Result) {
service, err := r.rc.GetService(res.Service.Name)
if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("unable to get service: %v", err)
logger.Errorf("unable to get %v service: %v", res.Service.Name, err)
}
return
}
@@ -127,7 +127,10 @@ func (r *registryRouter) store(services []*registry.Service) {
key := fmt.Sprintf("%s.%s", service.Name, sep.Name)
// decode endpoint
end := api.Decode(sep.Metadata)
// no endpoint or no name
if end == nil || len(end.Name) == 0 {
continue
}
// if we got nothing skip
if err := api.Validate(end); err != nil {
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
@@ -260,7 +263,7 @@ func (r *registryRouter) watch() {
res, err := w.Next()
if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("error getting next endoint: %v", err)
logger.Errorf("error getting next endpoint: %v", err)
}
close(ch)
break
@@ -434,7 +437,7 @@ func (r *registryRouter) Route(req *http.Request) (*api.Service, error) {
name := rp.Name
// get service
services, err := r.rc.GetService(name)
services, err := r.rc.GetService(name, registry.GetDomain(rp.Domain))
if err != nil {
return nil, err
}
+1 -1
View File
@@ -3,7 +3,7 @@ package registry
import (
"testing"
"github.com/asim/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry"
"github.com/stretchr/testify/assert"
)
+1 -1
View File
@@ -4,7 +4,7 @@ package router
import (
"net/http"
"github.com/asim/go-micro/v3/api"
"github.com/micro/go-micro/v3/api"
)
// Router is used to determine an endpoint for a request
+251
View File
@@ -0,0 +1,251 @@
package router_test
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"testing"
"time"
"github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v3/api/handler/rpc"
"github.com/micro/go-micro/v3/api/router"
rregistry "github.com/micro/go-micro/v3/api/router/registry"
rstatic "github.com/micro/go-micro/v3/api/router/static"
"github.com/micro/go-micro/v3/client"
gcli "github.com/micro/go-micro/v3/client/grpc"
rmemory "github.com/micro/go-micro/v3/registry/memory"
rt "github.com/micro/go-micro/v3/router"
regRouter "github.com/micro/go-micro/v3/router/registry"
"github.com/micro/go-micro/v3/server"
gsrv "github.com/micro/go-micro/v3/server/grpc"
pb "github.com/micro/go-micro/v3/server/grpc/proto"
)
// server is used to implement helloworld.GreeterServer.
type testServer struct {
msgCount int
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) Call(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) CallPcre(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) CallPcreInvalid(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
func initial(t *testing.T) (server.Server, client.Client) {
r := rmemory.NewRegistry()
// create a new client
s := gsrv.NewServer(
server.Name("foo"),
server.Registry(r),
)
rtr := regRouter.NewRouter(
rt.Registry(r),
)
// create a new server
c := gcli.NewClient(
client.Router(rtr),
)
h := &testServer{}
pb.RegisterTestHandler(s, h)
if err := s.Start(); err != nil {
t.Fatalf("failed to start: %v", err)
}
return s, c
}
func check(t *testing.T, addr string, path string, expected string) {
req, err := http.NewRequest("POST", fmt.Sprintf(path, addr), nil)
if err != nil {
t.Fatalf("Failed to created http.Request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
rsp, err := (&http.Client{}).Do(req)
if err != nil {
t.Fatalf("Failed to created http.Request: %v", err)
}
defer rsp.Body.Close()
buf, err := ioutil.ReadAll(rsp.Body)
if err != nil {
t.Fatal(err)
}
jsonMsg := expected
if string(buf) != jsonMsg {
t.Fatalf("invalid message received, parsing error %s != %s", buf, jsonMsg)
}
}
func TestRouterRegistryPcre(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rregistry.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`)
}
func TestRouterStaticPcre(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
err := router.Register(&api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"^/api/v0/test/call/?$"},
Handler: "rpc",
})
if err != nil {
t.Fatal(err)
}
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call", `{"msg":"Hello "}`)
}
func TestRouterStaticGpath(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
err := router.Register(&api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"/api/v0/test/call/{uuid}"},
Handler: "rpc",
})
if err != nil {
t.Fatal(err)
}
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`)
}
func TestRouterStaticPcreInvalid(t *testing.T) {
var ep *api.Endpoint
var err error
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
ep = &api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"^/api/v0/test/call/?"},
Handler: "rpc",
}
err = router.Register(ep)
if err == nil {
t.Fatalf("invalid endpoint %v", ep)
}
ep = &api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"/api/v0/test/call/?$"},
Handler: "rpc",
}
err = router.Register(ep)
if err == nil {
t.Fatalf("invalid endpoint %v", ep)
}
_ = c
}
+7 -7
View File
@@ -8,13 +8,13 @@ import (
"strings"
"sync"
"github.com/asim/go-micro/v3/api"
"github.com/asim/go-micro/v3/api/router"
"github.com/asim/go-micro/v3/api/router/util"
"github.com/asim/go-micro/v3/logger"
"github.com/asim/go-micro/v3/metadata"
"github.com/asim/go-micro/v3/registry"
rutil "github.com/asim/go-micro/v3/util/registry"
"github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v3/api/router"
"github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v3/registry"
rutil "github.com/micro/go-micro/v3/util/registry"
util "github.com/micro/go-micro/v3/util/router"
)
type endpoint struct {
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"net"
"os"
"github.com/asim/go-micro/v3/api/server/acme"
"github.com/asim/go-micro/v3/logger"
"github.com/micro/go-micro/v3/api/server/acme"
"github.com/micro/go-micro/v3/logger"
"golang.org/x/crypto/acme/autocert"
)
@@ -7,9 +7,9 @@ import (
"net"
"time"
"github.com/asim/go-micro/v3/api/server/acme"
"github.com/asim/go-micro/v3/logger"
"github.com/caddyserver/certmagic"
"github.com/micro/go-micro/v3/api/server/acme"
"github.com/micro/go-micro/v3/logger"
)
type certmagicProvider struct {
@@ -2,7 +2,6 @@ package certmagic
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
@@ -10,9 +9,9 @@ import (
"strings"
"time"
"github.com/asim/go-micro/v3/store"
"github.com/asim/go-micro/v3/sync"
"github.com/caddyserver/certmagic"
"github.com/micro/go-micro/v3/store"
"github.com/micro/go-micro/v3/sync"
)
// File represents a "File" that will be stored in store.Store - the contents and last modified time
@@ -31,7 +30,7 @@ type storage struct {
store store.Store
}
func (s *storage) Lock(ctx context.Context, key string) error {
func (s *storage) Lock(key string) error {
return s.lock.Lock(key, sync.LockTTL(10*time.Minute))
}
+1 -1
View File
@@ -1,6 +1,6 @@
package acme
import "github.com/go-acme/lego/v4/challenge"
import "github.com/go-acme/lego/v3/challenge"
// Option (or Options) are passed to New() to configure providers
type Option func(o *Options)
+22 -35
View File
@@ -4,54 +4,41 @@ import (
"net/http"
)
type Config struct {
AllowOrigin string
AllowCredentials bool
AllowMethods string
AllowHeaders string
// CombinedCORSHandler wraps a server and provides CORS headers
func CombinedCORSHandler(h http.Handler) http.Handler {
return corsHandler{h}
}
// CombinedCORSHandler wraps a server and provides CORS headers
func CombinedCORSHandler(h http.Handler, config *Config) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if config != nil {
SetHeaders(w, r, config)
}
if r.Method == "OPTIONS" {
return
}
type corsHandler struct {
handler http.Handler
}
h.ServeHTTP(w, r)
})
func (c corsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
SetHeaders(w, r)
if r.Method == "OPTIONS" {
return
}
c.handler.ServeHTTP(w, r)
}
// SetHeaders sets the CORS headers
func SetHeaders(w http.ResponseWriter, _ *http.Request, config *Config) {
func SetHeaders(w http.ResponseWriter, r *http.Request) {
set := func(w http.ResponseWriter, k, v string) {
if v := w.Header().Get(k); len(v) > 0 {
return
}
w.Header().Set(k, v)
}
//For forward-compatible code, default values may not be provided in the future
if config.AllowCredentials {
set(w, "Access-Control-Allow-Credentials", "true")
if origin := r.Header.Get("Origin"); len(origin) > 0 {
set(w, "Access-Control-Allow-Origin", origin)
} else {
set(w, "Access-Control-Allow-Credentials", "false")
}
if config.AllowOrigin == "" {
set(w, "Access-Control-Allow-Origin", "*")
} else {
set(w, "Access-Control-Allow-Origin", config.AllowOrigin)
}
if config.AllowMethods == "" {
set(w, "Access-Control-Allow-Methods", "POST, PATCH, GET, OPTIONS, PUT, DELETE")
} else {
set(w, "Access-Control-Allow-Methods", config.AllowMethods)
}
if config.AllowHeaders == "" {
set(w, "Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
} else {
set(w, "Access-Control-Allow-Headers", config.AllowHeaders)
}
set(w, "Access-Control-Allow-Credentials", "true")
set(w, "Access-Control-Allow-Methods", "POST, PATCH, GET, OPTIONS, PUT, DELETE")
set(w, "Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
}
+4 -4
View File
@@ -8,10 +8,10 @@ import (
"os"
"sync"
"github.com/asim/go-micro/v3/api/server"
"github.com/asim/go-micro/v3/api/server/cors"
"github.com/asim/go-micro/v3/logger"
"github.com/gorilla/handlers"
"github.com/micro/go-micro/v3/api/server"
"github.com/micro/go-micro/v3/api/server/cors"
"github.com/micro/go-micro/v3/logger"
)
type httpServer struct {
@@ -60,7 +60,7 @@ func (s *httpServer) Handle(path string, handler http.Handler) {
// wrap with cors
if s.opts.EnableCORS {
handler = cors.CombinedCORSHandler(handler, s.opts.CORSConfig)
handler = cors.CombinedCORSHandler(handler)
}
// wrap with logger
-73
View File
@@ -2,8 +2,6 @@ package http
import (
"fmt"
"github.com/asim/go-micro/v3/api/server"
"github.com/asim/go-micro/v3/api/server/cors"
"io/ioutil"
"net/http"
"testing"
@@ -41,74 +39,3 @@ func TestHTTPServer(t *testing.T) {
t.Fatal(err)
}
}
func TestCORSHTTPServer(t *testing.T) {
testResponse := "hello world"
testAllowOrigin := "*"
testAllowCredentials := true
testAllowMethods := "GET"
testAllowHeaders := "Accept, Content-Type, Content-Length"
s := NewServer("localhost:0",
server.EnableCORS(true),
server.CORSConfig(&cors.Config{
AllowCredentials: testAllowCredentials,
AllowOrigin: testAllowOrigin,
AllowMethods: testAllowMethods,
AllowHeaders: testAllowHeaders,
}),
)
s.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, testResponse)
}))
if err := s.Start(); err != nil {
t.Fatal(err)
}
rsp, err := http.Get(fmt.Sprintf("http://%s/", s.Address()))
if err != nil {
t.Fatal(err)
}
defer rsp.Body.Close()
b, err := ioutil.ReadAll(rsp.Body)
if err != nil {
t.Fatal(err)
}
if string(b) != testResponse {
t.Fatalf("Unexpected response, got %s, expected %s", string(b), testResponse)
}
allowCredentials := rsp.Header.Get("Access-Control-Allow-Credentials")
getTestCredentialsStr := func() string {
if testAllowCredentials == true {
return "true"
} else {
return "false"
}
}
if getTestCredentialsStr() != allowCredentials {
t.Fatalf("Unexpected Access-Control-Allow-Credentials, got %s, expected %s", allowCredentials, getTestCredentialsStr())
}
allowOrigin := rsp.Header.Get("Access-Control-Allow-Origin")
if testAllowOrigin != allowOrigin {
t.Fatalf("Unexpected Access-Control-Allow-Origins, got %s, expected %s", allowOrigin, testAllowOrigin)
}
allowMethods := rsp.Header.Get("Access-Control-Allow-Methods")
if testAllowMethods != allowMethods {
t.Fatalf("Unexpected Access-Control-Allow-Methods, got %s, expected %s", allowMethods, testAllowMethods)
}
allowHeaders := rsp.Header.Get("Access-Control-Allow-Headers")
if testAllowHeaders != allowHeaders {
t.Fatalf("Unexpected Access-Control-Allow-Headers, got %s, expected %s", allowHeaders, testAllowHeaders)
}
if err := s.Stop(); err != nil {
t.Fatal(err)
}
}
+4 -12
View File
@@ -2,11 +2,10 @@ package server
import (
"crypto/tls"
"github.com/asim/go-micro/v3/api/server/cors"
"net/http"
"github.com/asim/go-micro/v3/api/resolver"
"github.com/asim/go-micro/v3/api/server/acme"
"github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v3/api/server/acme"
)
type Option func(o *Options)
@@ -14,7 +13,6 @@ type Option func(o *Options)
type Options struct {
EnableACME bool
EnableCORS bool
CORSConfig *cors.Config
ACMEProvider acme.Provider
EnableTLS bool
ACMEHosts []string
@@ -25,9 +23,9 @@ type Options struct {
type Wrapper func(h http.Handler) http.Handler
func WrapHandler(w Wrapper) Option {
func WrapHandler(w ...Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
o.Wrappers = append(o.Wrappers, w...)
}
}
@@ -37,12 +35,6 @@ func EnableCORS(b bool) Option {
}
}
func CORSConfig(c *cors.Config) Option {
return func(o *Options) {
o.CORSConfig = c
}
}
func EnableACME(b bool) Option {
return func(o *Options) {
o.EnableACME = b
+6 -10
View File
@@ -31,24 +31,20 @@ type Auth interface {
Options() Options
// Generate a new account
Generate(id string, opts ...GenerateOption) (*Account, error)
// Verify an account has access to a resource using the rules
Verify(acc *Account, res *Resource, opts ...VerifyOption) error
// Inspect a token
Inspect(token string) (*Account, error)
// Token generated using refresh token or credentials
Token(opts ...TokenOption) (*Token, error)
// String returns the name of the implementation
String() string
}
// Rules manages access to resources
type Rules interface {
// Verify an account has access to a resource using the rules
Verify(acc *Account, res *Resource, opts ...VerifyOption) error
// Grant access to a resource
Grant(rule *Rule) error
// Revoke access to a resource
Revoke(rule *Rule) error
// List returns all the rules used to verify requests
List(...ListOption) ([]*Rule, error)
// Rules returns all the rules used to verify requests
Rules(...RulesOption) ([]*Rule, error)
// String returns the name of the implementation
String() string
}
// Account provided by an auth provider
+32 -38
View File
@@ -1,45 +1,35 @@
// Package jwt is a jwt implementation of the auth interface
package jwt
import (
"sync"
"time"
"github.com/asim/go-micro/v3/auth"
"github.com/asim/go-micro/v3/cmd"
jwtToken "github.com/asim/go-micro/plugins/auth/jwt/v3/token"
"github.com/micro/go-micro/v3/auth"
"github.com/micro/go-micro/v3/util/token"
"github.com/micro/go-micro/v3/util/token/jwt"
)
func init() {
cmd.DefaultAuths["jwt"] = NewAuth
}
// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
j := new(jwt)
j := new(jwtAuth)
j.Init(opts...)
return j
}
func NewRules() auth.Rules {
return new(jwtRules)
}
type jwt struct {
sync.Mutex
type jwtAuth struct {
options auth.Options
jwt jwtToken.Provider
}
token token.Provider
rules []*auth.Rule
type jwtRules struct {
sync.Mutex
rules []*auth.Rule
}
func (j *jwt) String() string {
func (j *jwtAuth) String() string {
return "jwt"
}
func (j *jwt) Init(opts ...auth.Option) {
func (j *jwtAuth) Init(opts ...auth.Option) {
j.Lock()
defer j.Unlock()
@@ -47,31 +37,35 @@ func (j *jwt) Init(opts ...auth.Option) {
o(&j.options)
}
j.jwt = jwtToken.New(
jwtToken.WithPrivateKey(j.options.PrivateKey),
jwtToken.WithPublicKey(j.options.PublicKey),
j.token = jwt.NewTokenProvider(
token.WithPrivateKey(j.options.PrivateKey),
token.WithPublicKey(j.options.PublicKey),
)
}
func (j *jwt) Options() auth.Options {
func (j *jwtAuth) Options() auth.Options {
j.Lock()
defer j.Unlock()
return j.options
}
func (j *jwt) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
func (j *jwtAuth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
options := auth.NewGenerateOptions(opts...)
if len(options.Issuer) == 0 {
options.Issuer = j.Options().Issuer
}
account := &auth.Account{
ID: id,
Type: options.Type,
Scopes: options.Scopes,
Metadata: options.Metadata,
Issuer: j.Options().Namespace,
Issuer: options.Issuer,
}
// generate a JWT secret which can be provided to the Token() method
// and exchanged for an access token
secret, err := j.jwt.Generate(account)
secret, err := j.token.Generate(account, token.WithExpiry(time.Hour*24*365))
if err != nil {
return nil, err
}
@@ -81,14 +75,14 @@ func (j *jwt) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, e
return account, nil
}
func (j *jwtRules) Grant(rule *auth.Rule) error {
func (j *jwtAuth) Grant(rule *auth.Rule) error {
j.Lock()
defer j.Unlock()
j.rules = append(j.rules, rule)
return nil
}
func (j *jwtRules) Revoke(rule *auth.Rule) error {
func (j *jwtAuth) Revoke(rule *auth.Rule) error {
j.Lock()
defer j.Unlock()
@@ -103,7 +97,7 @@ func (j *jwtRules) Revoke(rule *auth.Rule) error {
return nil
}
func (j *jwtRules) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
func (j *jwtAuth) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
j.Lock()
defer j.Unlock()
@@ -112,20 +106,20 @@ func (j *jwtRules) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.Ve
o(&options)
}
return auth.Verify(j.rules, acc, res)
return auth.VerifyAccess(j.rules, acc, res)
}
func (j *jwtRules) List(opts ...auth.ListOption) ([]*auth.Rule, error) {
func (j *jwtAuth) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
j.Lock()
defer j.Unlock()
return j.rules, nil
}
func (j *jwt) Inspect(token string) (*auth.Account, error) {
return j.jwt.Inspect(token)
func (j *jwtAuth) Inspect(token string) (*auth.Account, error) {
return j.token.Inspect(token)
}
func (j *jwt) Token(opts ...auth.TokenOption) (*auth.Token, error) {
func (j *jwtAuth) Token(opts ...auth.TokenOption) (*auth.Token, error) {
options := auth.NewTokenOptions(opts...)
secret := options.RefreshToken
@@ -133,17 +127,17 @@ func (j *jwt) Token(opts ...auth.TokenOption) (*auth.Token, error) {
secret = options.Secret
}
account, err := j.jwt.Inspect(secret)
account, err := j.token.Inspect(secret)
if err != nil {
return nil, err
}
access, err := j.jwt.Generate(account, jwtToken.WithExpiry(options.Expiry))
access, err := j.token.Generate(account, token.WithExpiry(options.Expiry))
if err != nil {
return nil, err
}
refresh, err := j.jwt.Generate(account, jwtToken.WithExpiry(options.Expiry+time.Hour))
refresh, err := j.token.Generate(account, token.WithExpiry(options.Expiry+time.Hour))
if err != nil {
return nil, err
}
-91
View File
@@ -1,91 +0,0 @@
package auth
import (
"github.com/google/uuid"
)
var (
DefaultAuth = NewAuth()
)
func NewAuth(opts ...Option) Auth {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noop{
opts: options,
}
}
func NewRules() Rules {
return new(noopRules)
}
type noop struct {
opts Options
}
type noopRules struct{}
// String returns the name of the implementation
func (n *noop) String() string {
return "noop"
}
// Init the auth
func (n *noop) Init(opts ...Option) {
for _, o := range opts {
o(&n.opts)
}
}
// Options set for auth
func (n *noop) Options() Options {
return n.opts
}
// Generate a new account
func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) {
options := NewGenerateOptions(opts...)
return &Account{
ID: id,
Secret: options.Secret,
Metadata: options.Metadata,
Scopes: options.Scopes,
Issuer: n.Options().Namespace,
}, nil
}
// Grant access to a resource
func (n *noopRules) Grant(rule *Rule) error {
return nil
}
// Revoke access to a resource
func (n *noopRules) Revoke(rule *Rule) error {
return nil
}
// Rules used to verify requests
// Verify an account has access to a resource
func (n *noopRules) Verify(acc *Account, res *Resource, opts ...VerifyOption) error {
return nil
}
func (n *noopRules) List(opts ...ListOption) ([]*Rule, error) {
return []*Rule{}, nil
}
// Inspect a token
func (n *noop) Inspect(token string) (*Account, error) {
return &Account{ID: uuid.New().String(), Issuer: n.Options().Namespace}, nil
}
// Token generation using an account id and secret
func (n *noop) Token(opts ...TokenOption) (*Token, error) {
return &Token{}, nil
}
+81
View File
@@ -0,0 +1,81 @@
package noop
import (
"github.com/google/uuid"
"github.com/micro/go-micro/v3/auth"
)
func NewAuth(opts ...auth.Option) auth.Auth {
var options auth.Options
for _, o := range opts {
o(&options)
}
return &noop{
opts: options,
}
}
type noop struct {
opts auth.Options
}
// String returns the name of the implementation
func (n *noop) String() string {
return "noop"
}
// Init the auth
func (n *noop) Init(opts ...auth.Option) {
for _, o := range opts {
o(&n.opts)
}
}
// Options set for auth
func (n *noop) Options() auth.Options {
return n.opts
}
// Generate a new account
func (n *noop) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
options := auth.NewGenerateOptions(opts...)
return &auth.Account{
ID: id,
Secret: options.Secret,
Metadata: options.Metadata,
Scopes: options.Scopes,
Issuer: n.Options().Issuer,
}, nil
}
// Grant access to a resource
func (n *noop) Grant(rule *auth.Rule) error {
return nil
}
// Revoke access to a resource
func (n *noop) Revoke(rule *auth.Rule) error {
return nil
}
// Rules used to verify requests
func (n *noop) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
return []*auth.Rule{}, nil
}
// Verify an account has access to a resource
func (n *noop) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
return nil
}
// Inspect a token
func (n *noop) Inspect(token string) (*auth.Account, error) {
return &auth.Account{ID: uuid.New().String(), Issuer: n.Options().Issuer}, nil
}
// Token generation using an account id and secret
func (n *noop) Token(opts ...auth.TokenOption) (*auth.Token, error) {
return &auth.Token{}, nil
}
+64 -12
View File
@@ -3,6 +3,8 @@ package auth
import (
"context"
"time"
"github.com/micro/go-micro/v3/store"
)
func NewOptions(opts ...Option) Options {
@@ -14,8 +16,8 @@ func NewOptions(opts ...Option) Options {
}
type Options struct {
// Namespace the service belongs to
Namespace string
// Issuer of the service's account
Issuer string
// ID is the services auth ID
ID string
// Secret is used to authenticate the service
@@ -26,8 +28,14 @@ type Options struct {
PublicKey string
// PrivateKey for encoding JWTs
PrivateKey string
// LoginURL is the relative url path where a user can login
LoginURL string
// Store to back auth
Store store.Store
// Addrs sets the addresses of auth
Addrs []string
// Context to store other options
Context context.Context
}
type Option func(o *Options)
@@ -39,10 +47,17 @@ func Addrs(addrs ...string) Option {
}
}
// Namespace the service belongs to
func Namespace(n string) Option {
// Issuer of the services account
func Issuer(i string) Option {
return func(o *Options) {
o.Namespace = n
o.Issuer = i
}
}
// Store to back auth
func Store(s store.Store) Option {
return func(o *Options) {
o.Store = s
}
}
@@ -75,6 +90,13 @@ func ClientToken(token *Token) Option {
}
}
// LoginURL sets the auth LoginURL
func LoginURL(url string) Option {
return func(o *Options) {
o.LoginURL = url
}
}
type GenerateOptions struct {
// Metadata associated with the account
Metadata map[string]string
@@ -86,6 +108,8 @@ type GenerateOptions struct {
Type string
// Secret used to authenticate the account
Secret string
// Issuer of the account, e.g. micro
Issuer string
}
type GenerateOption func(o *GenerateOptions)
@@ -125,6 +149,13 @@ func WithScopes(s ...string) GenerateOption {
}
}
// WithIssuer for the generated account
func WithIssuer(i string) GenerateOption {
return func(o *GenerateOptions) {
o.Issuer = i
}
}
// NewGenerateOptions from a slice of options
func NewGenerateOptions(opts ...GenerateOption) GenerateOptions {
var options GenerateOptions
@@ -143,6 +174,8 @@ type TokenOptions struct {
RefreshToken string
// Expiry is the time the token should live for
Expiry time.Duration
// Issuer of the account
Issuer string
}
type TokenOption func(o *TokenOptions)
@@ -167,6 +200,12 @@ func WithToken(rt string) TokenOption {
}
}
func WithTokenIssuer(iss string) TokenOption {
return func(o *TokenOptions) {
o.Issuer = iss
}
}
// NewTokenOptions from a slice of options
func NewTokenOptions(opts ...TokenOption) TokenOptions {
var options TokenOptions
@@ -183,7 +222,8 @@ func NewTokenOptions(opts ...TokenOption) TokenOptions {
}
type VerifyOptions struct {
Context context.Context
Context context.Context
Namespace string
}
type VerifyOption func(o *VerifyOptions)
@@ -193,15 +233,27 @@ func VerifyContext(ctx context.Context) VerifyOption {
o.Context = ctx
}
}
type ListOptions struct {
Context context.Context
func VerifyNamespace(ns string) VerifyOption {
return func(o *VerifyOptions) {
o.Namespace = ns
}
}
type ListOption func(o *ListOptions)
type RulesOptions struct {
Context context.Context
Namespace string
}
func RulesContext(ctx context.Context) ListOption {
return func(o *ListOptions) {
type RulesOption func(o *RulesOptions)
func RulesContext(ctx context.Context) RulesOption {
return func(o *RulesOptions) {
o.Context = ctx
}
}
func RulesNamespace(ns string) RulesOption {
return func(o *RulesOptions) {
o.Namespace = ns
}
}
+3 -3
View File
@@ -6,10 +6,10 @@ import (
"strings"
)
// Verify an account has access to a resource using the rules provided. If the account does not have
// VerifyAccess an account has access to a resource using the rules provided. If the account does not have
// access an error will be returned. If there are no rules provided which match the resource, an error
// will be returned
func Verify(rules []*Rule, acc *Account, res *Resource) error {
func VerifyAccess(rules []*Rule, acc *Account, res *Resource) error {
// the rule is only to be applied if the type matches the resource or is catch-all (*)
validTypes := []string{"*", res.Type}
@@ -83,7 +83,7 @@ func Verify(rules []*Rule, acc *Account, res *Resource) error {
// not case sensitive.
func include(slice []string, val string) bool {
for _, s := range slice {
if strings.EqualFold(s, val) {
if strings.ToLower(s) == strings.ToLower(val) {
return true
}
}
+1 -1
View File
@@ -280,7 +280,7 @@ func TestVerify(t *testing.T) {
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
if err := Verify(tc.Rules, tc.Account, tc.Resource); err != tc.Error {
if err := VerifyAccess(tc.Rules, tc.Account, tc.Resource); err != tc.Error {
t.Errorf("Expected %v but got %v", tc.Error, err)
}
})
+3 -39
View File
@@ -14,54 +14,18 @@ type Broker interface {
}
// Handler is used to process messages via a subscription of a topic.
// The handler is passed a publication interface which contains the
// message and optional Ack method to acknowledge receipt of the message.
type Handler func(Event) error
type Handler func(*Message) error
type ErrorHandler func(*Message, error)
type Message struct {
Header map[string]string
Body []byte
}
// Event is given to a subscription handler for processing
type Event interface {
Topic() string
Message() *Message
Ack() error
Error() error
}
// Subscriber is a convenience return type for the Subscribe method
type Subscriber interface {
Options() SubscribeOptions
Topic() string
Unsubscribe() error
}
var (
DefaultBroker Broker = NewBroker()
)
func Init(opts ...Option) error {
return DefaultBroker.Init(opts...)
}
func Connect() error {
return DefaultBroker.Connect()
}
func Disconnect() error {
return DefaultBroker.Disconnect()
}
func Publish(topic string, msg *Message, opts ...PublishOption) error {
return DefaultBroker.Publish(topic, msg, opts...)
}
func Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
return DefaultBroker.Subscribe(topic, handler, opts...)
}
func String() string {
return DefaultBroker.String()
}
-711
View File
@@ -1,711 +0,0 @@
// Package http provides a http based message broker
package broker
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"runtime"
"sync"
"time"
"github.com/asim/go-micro/v3/codec/json"
merr "github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/registry/cache"
maddr "github.com/asim/go-micro/v3/util/addr"
mnet "github.com/asim/go-micro/v3/util/net"
mls "github.com/asim/go-micro/v3/util/tls"
"github.com/google/uuid"
"golang.org/x/net/http2"
)
// HTTP Broker is a point to point async broker
type httpBroker struct {
id string
address string
opts Options
mux *http.ServeMux
c *http.Client
r registry.Registry
sync.RWMutex
subscribers map[string][]*httpSubscriber
running bool
exit chan chan error
// offline message inbox
mtx sync.RWMutex
inbox map[string][][]byte
}
type httpSubscriber struct {
opts SubscribeOptions
id string
topic string
fn Handler
svc *registry.Service
hb *httpBroker
}
type httpEvent struct {
m *Message
t string
err error
}
var (
DefaultPath = "/"
DefaultAddress = "127.0.0.1:0"
serviceName = "micro.http.broker"
broadcastVersion = "ff.http.broadcast"
registerTTL = time.Minute
registerInterval = time.Second * 30
)
func init() {
rand.Seed(time.Now().Unix())
}
func newTransport(config *tls.Config) *http.Transport {
if config == nil {
config = &tls.Config{
InsecureSkipVerify: true,
}
}
dialTLS := func(network string, addr string) (net.Conn, error) {
return tls.Dial(network, addr, config)
}
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
DialTLS: dialTLS,
}
runtime.SetFinalizer(&t, func(tr **http.Transport) {
(*tr).CloseIdleConnections()
})
// setup http2
http2.ConfigureTransport(t)
return t
}
func newHttpBroker(opts ...Option) Broker {
options := Options{
Codec: json.Marshaler{},
Context: context.TODO(),
Registry: registry.DefaultRegistry,
}
for _, o := range opts {
o(&options)
}
// set address
addr := DefaultAddress
if len(options.Addrs) > 0 && len(options.Addrs[0]) > 0 {
addr = options.Addrs[0]
}
h := &httpBroker{
id: uuid.New().String(),
address: addr,
opts: options,
r: options.Registry,
c: &http.Client{Transport: newTransport(options.TLSConfig)},
subscribers: make(map[string][]*httpSubscriber),
exit: make(chan chan error),
mux: http.NewServeMux(),
inbox: make(map[string][][]byte),
}
// specify the message handler
h.mux.Handle(DefaultPath, h)
// get optional handlers
if h.opts.Context != nil {
handlers, ok := h.opts.Context.Value("http_handlers").(map[string]http.Handler)
if ok {
for pattern, handler := range handlers {
h.mux.Handle(pattern, handler)
}
}
}
return h
}
func (h *httpEvent) Ack() error {
return nil
}
func (h *httpEvent) Error() error {
return h.err
}
func (h *httpEvent) Message() *Message {
return h.m
}
func (h *httpEvent) Topic() string {
return h.t
}
func (h *httpSubscriber) Options() SubscribeOptions {
return h.opts
}
func (h *httpSubscriber) Topic() string {
return h.topic
}
func (h *httpSubscriber) Unsubscribe() error {
return h.hb.unsubscribe(h)
}
func (h *httpBroker) saveMessage(topic string, msg []byte) {
h.mtx.Lock()
defer h.mtx.Unlock()
// get messages
c := h.inbox[topic]
// save message
c = append(c, msg)
// max length 64
if len(c) > 64 {
c = c[:64]
}
// save inbox
h.inbox[topic] = c
}
func (h *httpBroker) getMessage(topic string, num int) [][]byte {
h.mtx.Lock()
defer h.mtx.Unlock()
// get messages
c, ok := h.inbox[topic]
if !ok {
return nil
}
// more message than requests
if len(c) >= num {
msg := c[:num]
h.inbox[topic] = c[num:]
return msg
}
// reset inbox
h.inbox[topic] = nil
// return all messages
return c
}
func (h *httpBroker) subscribe(s *httpSubscriber) error {
h.Lock()
defer h.Unlock()
if err := h.r.Register(s.svc, registry.RegisterTTL(registerTTL)); err != nil {
return err
}
h.subscribers[s.topic] = append(h.subscribers[s.topic], s)
return nil
}
func (h *httpBroker) unsubscribe(s *httpSubscriber) error {
h.Lock()
defer h.Unlock()
//nolint:prealloc
var subscribers []*httpSubscriber
// look for subscriber
for _, sub := range h.subscribers[s.topic] {
// deregister and skip forward
if sub == s {
_ = h.r.Deregister(sub.svc)
continue
}
// keep subscriber
subscribers = append(subscribers, sub)
}
// set subscribers
h.subscribers[s.topic] = subscribers
return nil
}
func (h *httpBroker) run(l net.Listener) {
t := time.NewTicker(registerInterval)
defer t.Stop()
for {
select {
// heartbeat for each subscriber
case <-t.C:
h.RLock()
for _, subs := range h.subscribers {
for _, sub := range subs {
_ = h.r.Register(sub.svc, registry.RegisterTTL(registerTTL))
}
}
h.RUnlock()
// received exit signal
case ch := <-h.exit:
ch <- l.Close()
h.RLock()
for _, subs := range h.subscribers {
for _, sub := range subs {
_ = h.r.Deregister(sub.svc)
}
}
h.RUnlock()
return
}
}
}
func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
err := merr.BadRequest("go.micro.broker", "Method not allowed")
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
return
}
defer req.Body.Close()
req.ParseForm()
b, err := ioutil.ReadAll(req.Body)
if err != nil {
errr := merr.InternalServerError("go.micro.broker", "Error reading request body: %v", err)
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
var m *Message
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
errr := merr.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
topic := m.Header["Micro-Topic"]
//delete(m.Header, ":topic")
if len(topic) == 0 {
errr := merr.InternalServerError("go.micro.broker", "Topic not found")
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
p := &httpEvent{m: m, t: topic}
id := req.Form.Get("id")
//nolint:prealloc
var subs []Handler
h.RLock()
for _, subscriber := range h.subscribers[topic] {
if id != subscriber.id {
continue
}
subs = append(subs, subscriber.fn)
}
h.RUnlock()
// execute the handler
for _, fn := range subs {
p.err = fn(p)
}
}
func (h *httpBroker) Address() string {
h.RLock()
defer h.RUnlock()
return h.address
}
func (h *httpBroker) Connect() error {
h.RLock()
if h.running {
h.RUnlock()
return nil
}
h.RUnlock()
h.Lock()
defer h.Unlock()
var l net.Listener
var err error
if h.opts.Secure || h.opts.TLSConfig != nil {
config := h.opts.TLSConfig
fn := func(addr string) (net.Listener, error) {
if config == nil {
hosts := []string{addr}
// check if its a valid host:port
if host, _, err := net.SplitHostPort(addr); err == nil {
if len(host) == 0 {
hosts = maddr.IPs()
} else {
hosts = []string{host}
}
}
// generate a certificate
cert, err := mls.Certificate(hosts...)
if err != nil {
return nil, err
}
config = &tls.Config{Certificates: []tls.Certificate{cert}}
}
return tls.Listen("tcp", addr, config)
}
l, err = mnet.Listen(h.address, fn)
} else {
fn := func(addr string) (net.Listener, error) {
return net.Listen("tcp", addr)
}
l, err = mnet.Listen(h.address, fn)
}
if err != nil {
return err
}
addr := h.address
h.address = l.Addr().String()
go http.Serve(l, h.mux)
go func() {
h.run(l)
h.Lock()
h.opts.Addrs = []string{addr}
h.address = addr
h.Unlock()
}()
// get registry
reg := h.opts.Registry
if reg == nil {
reg = registry.DefaultRegistry
}
// set cache
h.r = cache.New(reg)
// set running
h.running = true
return nil
}
func (h *httpBroker) Disconnect() error {
h.RLock()
if !h.running {
h.RUnlock()
return nil
}
h.RUnlock()
h.Lock()
defer h.Unlock()
// stop cache
rc, ok := h.r.(cache.Cache)
if ok {
rc.Stop()
}
// exit and return err
ch := make(chan error)
h.exit <- ch
err := <-ch
// set not running
h.running = false
return err
}
func (h *httpBroker) Init(opts ...Option) error {
h.RLock()
if h.running {
h.RUnlock()
return errors.New("cannot init while connected")
}
h.RUnlock()
h.Lock()
defer h.Unlock()
for _, o := range opts {
o(&h.opts)
}
if len(h.opts.Addrs) > 0 && len(h.opts.Addrs[0]) > 0 {
h.address = h.opts.Addrs[0]
}
if len(h.id) == 0 {
h.id = "go.micro.http.broker-" + uuid.New().String()
}
// get registry
reg := h.opts.Registry
if reg == nil {
reg = registry.DefaultRegistry
}
// get cache
if rc, ok := h.r.(cache.Cache); ok {
rc.Stop()
}
// set registry
h.r = cache.New(reg)
// reconfigure tls config
if c := h.opts.TLSConfig; c != nil {
h.c = &http.Client{
Transport: newTransport(c),
}
}
return nil
}
func (h *httpBroker) Options() Options {
return h.opts
}
func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption) error {
// create the message first
m := &Message{
Header: make(map[string]string),
Body: msg.Body,
}
for k, v := range msg.Header {
m.Header[k] = v
}
m.Header["Micro-Topic"] = topic
// encode the message
b, err := h.opts.Codec.Marshal(m)
if err != nil {
return err
}
// save the message
h.saveMessage(topic, b)
// now attempt to get the service
h.RLock()
s, err := h.r.GetService(serviceName)
if err != nil {
h.RUnlock()
return err
}
h.RUnlock()
pub := func(node *registry.Node, t string, b []byte) error {
scheme := "http"
// check if secure is added in metadata
if node.Metadata["secure"] == "true" {
scheme = "https"
}
vals := url.Values{}
vals.Add("id", node.Id)
uri := fmt.Sprintf("%s://%s%s?%s", scheme, node.Address, DefaultPath, vals.Encode())
r, err := h.c.Post(uri, "application/json", bytes.NewReader(b))
if err != nil {
return err
}
// discard response body
io.Copy(ioutil.Discard, r.Body)
r.Body.Close()
return nil
}
srv := func(s []*registry.Service, b []byte) {
for _, service := range s {
var nodes []*registry.Node
for _, node := range service.Nodes {
// only use nodes tagged with broker http
if node.Metadata["broker"] != "http" {
continue
}
// look for nodes for the topic
if node.Metadata["topic"] != topic {
continue
}
nodes = append(nodes, node)
}
// only process if we have nodes
if len(nodes) == 0 {
continue
}
switch service.Version {
// broadcast version means broadcast to all nodes
case broadcastVersion:
var success bool
// publish to all nodes
for _, node := range nodes {
// publish async
if err := pub(node, topic, b); err == nil {
success = true
}
}
// save if it failed to publish at least once
if !success {
h.saveMessage(topic, b)
}
default:
// select node to publish to
node := nodes[rand.Int()%len(nodes)]
// publish async to one node
if err := pub(node, topic, b); err != nil {
// if failed save it
h.saveMessage(topic, b)
}
}
}
}
// do the rest async
go func() {
// get a third of the backlog
messages := h.getMessage(topic, 8)
delay := (len(messages) > 1)
// publish all the messages
for _, msg := range messages {
// serialize here
srv(s, msg)
// sending a backlog of messages
if delay {
time.Sleep(time.Millisecond * 100)
}
}
}()
return nil
}
func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
var err error
var host, port string
options := NewSubscribeOptions(opts...)
// parse address for host, port
host, port, err = net.SplitHostPort(h.Address())
if err != nil {
return nil, err
}
addr, err := maddr.Extract(host)
if err != nil {
return nil, err
}
var secure bool
if h.opts.Secure || h.opts.TLSConfig != nil {
secure = true
}
// register service
node := &registry.Node{
Id: topic + "-" + h.id,
Address: mnet.HostPort(addr, port),
Metadata: map[string]string{
"secure": fmt.Sprintf("%t", secure),
"broker": "http",
"topic": topic,
},
}
// check for queue group or broadcast queue
version := options.Queue
if len(version) == 0 {
version = broadcastVersion
}
service := &registry.Service{
Name: serviceName,
Version: version,
Nodes: []*registry.Node{node},
}
// generate subscriber
subscriber := &httpSubscriber{
opts: options,
hb: h,
id: node.Id,
topic: topic,
fn: handler,
svc: service,
}
// subscribe now
if err := h.subscribe(subscriber); err != nil {
return nil, err
}
// return the subscriber
return subscriber, nil
}
func (h *httpBroker) String() string {
return "http"
}
// NewBroker returns a new http broker
func NewBroker(opts ...Option) Broker {
return newHttpBroker(opts...)
}
@@ -18,22 +18,18 @@ import (
"time"
"github.com/google/uuid"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/codec/json"
"github.com/asim/go-micro/v3/cmd"
merr "github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/registry/cache"
maddr "github.com/asim/go-micro/v3/util/addr"
mnet "github.com/asim/go-micro/v3/util/net"
mls "github.com/asim/go-micro/v3/util/tls"
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/codec/json"
merr "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/cache"
"github.com/micro/go-micro/v3/registry/mdns"
maddr "github.com/micro/go-micro/v3/util/addr"
mnet "github.com/micro/go-micro/v3/util/net"
mls "github.com/micro/go-micro/v3/util/tls"
"golang.org/x/net/http2"
)
func init() {
cmd.DefaultBrokers["http"] = NewBroker
}
// HTTP Broker is a point to point async broker
type httpBroker struct {
id string
@@ -64,15 +60,10 @@ type httpSubscriber struct {
hb *httpBroker
}
type httpEvent struct {
m *broker.Message
t string
err error
}
var (
DefaultSubPath = "/_sub"
serviceName = "go.micro.http.broker"
DefaultPath = "/"
DefaultAddress = "127.0.0.1:0"
serviceName = "micro.http.broker"
broadcastVersion = "ff.http.broadcast"
registerTTL = time.Minute
registerInterval = time.Second * 30
@@ -116,7 +107,7 @@ func newHttpBroker(opts ...broker.Option) broker.Broker {
options := broker.Options{
Codec: json.Marshaler{},
Context: context.TODO(),
Registry: registry.DefaultRegistry,
Registry: mdns.NewRegistry(),
}
for _, o := range opts {
@@ -124,7 +115,8 @@ func newHttpBroker(opts ...broker.Option) broker.Broker {
}
// set address
addr := ":0"
addr := DefaultAddress
if len(options.Addrs) > 0 && len(options.Addrs[0]) > 0 {
addr = options.Addrs[0]
}
@@ -142,7 +134,7 @@ func newHttpBroker(opts ...broker.Option) broker.Broker {
}
// specify the message handler
h.mux.Handle(DefaultSubPath, h)
h.mux.Handle(DefaultPath, h)
// get optional handlers
if h.opts.Context != nil {
@@ -157,22 +149,6 @@ func newHttpBroker(opts ...broker.Option) broker.Broker {
return h
}
func (h *httpEvent) Ack() error {
return nil
}
func (h *httpEvent) Error() error {
return h.err
}
func (h *httpEvent) Message() *broker.Message {
return h.m
}
func (h *httpEvent) Topic() string {
return h.t
}
func (h *httpSubscriber) Options() broker.SubscribeOptions {
return h.opts
}
@@ -312,16 +288,15 @@ func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
var m *broker.Message
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
var msg *broker.Message
if err = h.opts.Codec.Unmarshal(b, &msg); err != nil {
errr := merr.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
topic := m.Header[":topic"]
delete(m.Header, ":topic")
topic := msg.Header["Micro-Topic"]
if len(topic) == 0 {
errr := merr.InternalServerError("go.micro.broker", "Topic not found")
@@ -330,7 +305,6 @@ func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
p := &httpEvent{m: m, t: topic}
id := req.Form.Get("id")
//nolint:prealloc
@@ -347,7 +321,7 @@ func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// execute the handler
for _, fn := range subs {
p.err = fn(p)
fn(msg)
}
}
@@ -425,7 +399,7 @@ func (h *httpBroker) Connect() error {
// get registry
reg := h.opts.Registry
if reg == nil {
reg = registry.DefaultRegistry
reg = mdns.NewRegistry()
}
// set cache
h.r = cache.New(reg)
@@ -488,7 +462,7 @@ func (h *httpBroker) Init(opts ...broker.Option) error {
// get registry
reg := h.opts.Registry
if reg == nil {
reg = registry.DefaultRegistry
reg = mdns.NewRegistry()
}
// get cache
@@ -524,7 +498,7 @@ func (h *httpBroker) Publish(topic string, msg *broker.Message, opts ...broker.P
m.Header[k] = v
}
m.Header[":topic"] = topic
m.Header["Micro-Topic"] = topic
// encode the message
b, err := h.opts.Codec.Marshal(m)
@@ -555,7 +529,7 @@ func (h *httpBroker) Publish(topic string, msg *broker.Message, opts ...broker.P
vals := url.Values{}
vals.Add("id", node.Id)
uri := fmt.Sprintf("%s://%s%s?%s", scheme, node.Address, DefaultSubPath, vals.Encode())
uri := fmt.Sprintf("%s://%s%s?%s", scheme, node.Address, DefaultPath, vals.Encode())
r, err := h.c.Post(uri, "application/json", bytes.NewReader(b))
if err != nil {
return err
@@ -6,9 +6,9 @@ import (
"time"
"github.com/google/uuid"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/plugins/registry/memory/v3"
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/memory"
)
var (
@@ -83,9 +83,8 @@ func sub(be *testing.B, c int) {
done := make(chan bool, c)
for i := 0; i < c; i++ {
sub, err := b.Subscribe(topic, func(p broker.Event) error {
sub, err := b.Subscribe(topic, func(m *broker.Message) error {
done <- true
m := p.Message()
if string(m.Body) != string(msg.Body) {
be.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
@@ -140,9 +139,8 @@ func pub(be *testing.B, c int) {
done := make(chan bool, c*4)
sub, err := b.Subscribe(topic, func(p broker.Event) error {
sub, err := b.Subscribe(topic, func(m *broker.Message) error {
done <- true
m := p.Message()
if string(m.Body) != string(msg.Body) {
be.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
@@ -208,8 +206,7 @@ func TestBroker(t *testing.T) {
done := make(chan bool)
sub, err := b.Subscribe("test", func(p broker.Event) error {
m := p.Message()
sub, err := b.Subscribe("test", func(m *broker.Message) error {
if string(m.Body) != string(msg.Body) {
t.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
@@ -257,11 +254,9 @@ func TestConcurrentSubBroker(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
sub, err := b.Subscribe("test", func(p broker.Event) error {
sub, err := b.Subscribe("test", func(m *broker.Message) error {
defer wg.Done()
m := p.Message()
if string(m.Body) != string(msg.Body) {
t.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
@@ -312,11 +307,9 @@ func TestConcurrentPubBroker(t *testing.T) {
var wg sync.WaitGroup
sub, err := b.Subscribe("test", func(p broker.Event) error {
sub, err := b.Subscribe("test", func(m *broker.Message) error {
defer wg.Done()
m := p.Message()
if string(m.Body) != string(msg.Body) {
t.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
+23
View File
@@ -0,0 +1,23 @@
package http
import (
"context"
"net/http"
"github.com/micro/go-micro/v3/broker"
)
// Handle registers the handler for the given pattern.
func Handle(pattern string, handler http.Handler) broker.Option {
return func(o *broker.Options) {
if o.Context == nil {
o.Context = context.Background()
}
handlers, ok := o.Context.Value("http_handlers").(map[string]http.Handler)
if !ok {
handlers = make(map[string]http.Handler)
}
handlers[pattern] = handler
o.Context = context.WithValue(o.Context, "http_handlers", handlers)
}
}
-383
View File
@@ -1,383 +0,0 @@
package broker_test
import (
"sync"
"testing"
"time"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/registry"
"github.com/google/uuid"
)
var (
// mock data
testData = map[string][]*registry.Service{
"foo": {
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
{
Id: "foo-1.0.0-123",
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost:9999",
},
},
},
{
Name: "foo",
Version: "1.0.1",
Nodes: []*registry.Node{
{
Id: "foo-1.0.1-321",
Address: "localhost:6666",
},
},
},
{
Name: "foo",
Version: "1.0.3",
Nodes: []*registry.Node{
{
Id: "foo-1.0.3-345",
Address: "localhost:8888",
},
},
},
},
}
)
func newTestRegistry() registry.Registry {
return registry.NewMemoryRegistry(registry.Services(testData))
}
func sub(be *testing.B, c int) {
be.StopTimer()
m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m))
topic := uuid.New().String()
if err := b.Init(); err != nil {
be.Fatalf("Unexpected init error: %v", err)
}
if err := b.Connect(); err != nil {
be.Fatalf("Unexpected connect error: %v", err)
}
msg := &broker.Message{
Header: map[string]string{
"Content-Type": "application/json",
},
Body: []byte(`{"message": "Hello World"}`),
}
var subs []broker.Subscriber
done := make(chan bool, c)
for i := 0; i < c; i++ {
sub, err := b.Subscribe(topic, func(p broker.Event) error {
done <- true
m := p.Message()
if string(m.Body) != string(msg.Body) {
be.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
return nil
}, broker.Queue("shared"))
if err != nil {
be.Fatalf("Unexpected subscribe error: %v", err)
}
subs = append(subs, sub)
}
for i := 0; i < be.N; i++ {
be.StartTimer()
if err := b.Publish(topic, msg); err != nil {
be.Fatalf("Unexpected publish error: %v", err)
}
<-done
be.StopTimer()
}
for _, sub := range subs {
sub.Unsubscribe()
}
if err := b.Disconnect(); err != nil {
be.Fatalf("Unexpected disconnect error: %v", err)
}
}
func pub(be *testing.B, c int) {
be.StopTimer()
m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m))
topic := uuid.New().String()
if err := b.Init(); err != nil {
be.Fatalf("Unexpected init error: %v", err)
}
if err := b.Connect(); err != nil {
be.Fatalf("Unexpected connect error: %v", err)
}
msg := &broker.Message{
Header: map[string]string{
"Content-Type": "application/json",
},
Body: []byte(`{"message": "Hello World"}`),
}
done := make(chan bool, c*4)
sub, err := b.Subscribe(topic, func(p broker.Event) error {
done <- true
m := p.Message()
if string(m.Body) != string(msg.Body) {
be.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
return nil
}, broker.Queue("shared"))
if err != nil {
be.Fatalf("Unexpected subscribe error: %v", err)
}
var wg sync.WaitGroup
ch := make(chan int, c*4)
be.StartTimer()
for i := 0; i < c; i++ {
go func() {
for range ch {
if err := b.Publish(topic, msg); err != nil {
be.Fatalf("Unexpected publish error: %v", err)
}
select {
case <-done:
case <-time.After(time.Second):
}
wg.Done()
}
}()
}
for i := 0; i < be.N; i++ {
wg.Add(1)
ch <- i
}
wg.Wait()
be.StopTimer()
sub.Unsubscribe()
close(ch)
close(done)
if err := b.Disconnect(); err != nil {
be.Fatalf("Unexpected disconnect error: %v", err)
}
}
func TestBroker(t *testing.T) {
m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m))
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error: %v", err)
}
if err := b.Connect(); err != nil {
t.Fatalf("Unexpected connect error: %v", err)
}
msg := &broker.Message{
Header: map[string]string{
"Content-Type": "application/json",
},
Body: []byte(`{"message": "Hello World"}`),
}
done := make(chan bool)
sub, err := b.Subscribe("test", func(p broker.Event) error {
m := p.Message()
if string(m.Body) != string(msg.Body) {
t.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
close(done)
return nil
})
if err != nil {
t.Fatalf("Unexpected subscribe error: %v", err)
}
if err := b.Publish("test", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
<-done
sub.Unsubscribe()
if err := b.Disconnect(); err != nil {
t.Fatalf("Unexpected disconnect error: %v", err)
}
}
func TestConcurrentSubBroker(t *testing.T) {
m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m))
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error: %v", err)
}
if err := b.Connect(); err != nil {
t.Fatalf("Unexpected connect error: %v", err)
}
msg := &broker.Message{
Header: map[string]string{
"Content-Type": "application/json",
},
Body: []byte(`{"message": "Hello World"}`),
}
var subs []broker.Subscriber
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
sub, err := b.Subscribe("test", func(p broker.Event) error {
defer wg.Done()
m := p.Message()
if string(m.Body) != string(msg.Body) {
t.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
return nil
})
if err != nil {
t.Fatalf("Unexpected subscribe error: %v", err)
}
wg.Add(1)
subs = append(subs, sub)
}
if err := b.Publish("test", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
wg.Wait()
for _, sub := range subs {
sub.Unsubscribe()
}
if err := b.Disconnect(); err != nil {
t.Fatalf("Unexpected disconnect error: %v", err)
}
}
func TestConcurrentPubBroker(t *testing.T) {
m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m))
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error: %v", err)
}
if err := b.Connect(); err != nil {
t.Fatalf("Unexpected connect error: %v", err)
}
msg := &broker.Message{
Header: map[string]string{
"Content-Type": "application/json",
},
Body: []byte(`{"message": "Hello World"}`),
}
var wg sync.WaitGroup
sub, err := b.Subscribe("test", func(p broker.Event) error {
defer wg.Done()
m := p.Message()
if string(m.Body) != string(msg.Body) {
t.Fatalf("Unexpected msg %s, expected %s", string(m.Body), string(msg.Body))
}
return nil
})
if err != nil {
t.Fatalf("Unexpected subscribe error: %v", err)
}
for i := 0; i < 10; i++ {
wg.Add(1)
if err := b.Publish("test", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
wg.Wait()
sub.Unsubscribe()
if err := b.Disconnect(); err != nil {
t.Fatalf("Unexpected disconnect error: %v", err)
}
}
func BenchmarkSub1(b *testing.B) {
sub(b, 1)
}
func BenchmarkSub8(b *testing.B) {
sub(b, 8)
}
func BenchmarkSub32(b *testing.B) {
sub(b, 32)
}
func BenchmarkSub64(b *testing.B) {
sub(b, 64)
}
func BenchmarkSub128(b *testing.B) {
sub(b, 128)
}
func BenchmarkPub1(b *testing.B) {
pub(b, 1)
}
func BenchmarkPub8(b *testing.B) {
pub(b, 8)
}
func BenchmarkPub32(b *testing.B) {
pub(b, 32)
}
func BenchmarkPub64(b *testing.B) {
pub(b, 64)
}
func BenchmarkPub128(b *testing.B) {
pub(b, 128)
}
@@ -9,17 +9,11 @@ import (
"time"
"github.com/google/uuid"
"github.com/asim/go-micro/v3/cmd"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/logger"
maddr "github.com/asim/go-micro/v3/util/addr"
mnet "github.com/asim/go-micro/v3/util/net"
"github.com/micro/go-micro/v3/broker"
maddr "github.com/micro/go-micro/v3/util/addr"
mnet "github.com/micro/go-micro/v3/util/net"
)
func init() {
cmd.DefaultBrokers["memory"] = NewBroker
}
type memoryBroker struct {
opts broker.Options
@@ -29,13 +23,6 @@ type memoryBroker struct {
Subscribers map[string][]*memorySubscriber
}
type memoryEvent struct {
opts broker.Options
topic string
err error
message interface{}
}
type memorySubscriber struct {
id string
topic string
@@ -108,31 +95,12 @@ func (m *memoryBroker) Publish(topic string, msg *broker.Message, opts ...broker
return nil
}
var v interface{}
if m.opts.Codec != nil {
buf, err := m.opts.Codec.Marshal(msg)
if err != nil {
return err
}
v = buf
} else {
v = msg
}
p := &memoryEvent{
topic: topic,
message: v,
opts: m.opts,
}
for _, sub := range subs {
if err := sub.handler(p); err != nil {
p.err = err
if eh := m.opts.ErrorHandler; eh != nil {
eh(p)
continue
if err := sub.handler(msg); err != nil {
if eh := sub.opts.ErrorHandler; eh != nil {
eh(msg, err)
}
return err
continue
}
}
@@ -185,36 +153,6 @@ func (m *memoryBroker) String() string {
return "memory"
}
func (m *memoryEvent) Topic() string {
return m.topic
}
func (m *memoryEvent) Message() *broker.Message {
switch v := m.message.(type) {
case *broker.Message:
return v
case []byte:
msg := &broker.Message{}
if err := m.opts.Codec.Unmarshal(v, msg); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("[memory]: failed to unmarshal: %v\n", err)
}
return nil
}
return msg
}
return nil
}
func (m *memoryEvent) Ack() error {
return nil
}
func (m *memoryEvent) Error() error {
return m.err
}
func (m *memorySubscriber) Options() broker.SubscribeOptions {
return m.opts
}
@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
"github.com/asim/go-micro/v3/broker"
"github.com/micro/go-micro/v3/broker"
)
func TestMemoryBroker(t *testing.T) {
@@ -17,7 +17,7 @@ func TestMemoryBroker(t *testing.T) {
topic := "test"
count := 10
fn := func(p broker.Event) error {
fn := func(m *broker.Message) error {
return nil
}
@@ -3,7 +3,7 @@ package nats
import (
"context"
"github.com/asim/go-micro/v3/broker"
"github.com/micro/go-micro/v3/broker"
)
// setBrokerOption returns a function to setup a context with given value
@@ -7,18 +7,13 @@ import (
"strings"
"sync"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/cmd"
"github.com/asim/go-micro/v3/codec/json"
"github.com/asim/go-micro/v3/logger"
"github.com/asim/go-micro/v3/registry"
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/codec/json"
"github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v3/registry/mdns"
nats "github.com/nats-io/nats.go"
)
func init() {
cmd.DefaultBrokers["nats"] = NewBroker
}
type natsBroker struct {
sync.Once
sync.RWMutex
@@ -41,29 +36,6 @@ type subscriber struct {
opts broker.SubscribeOptions
}
type publication struct {
t string
err error
m *broker.Message
}
func (p *publication) Topic() string {
return p.t
}
func (p *publication) Message() *broker.Message {
return p.m
}
func (p *publication) Ack() error {
// nats does not support acking
return nil
}
func (p *publication) Error() error {
return p.err
}
func (s *subscriber) Options() broker.SubscribeOptions {
return s.opts
}
@@ -136,6 +108,10 @@ func (n *natsBroker) Connect() error {
c, err := opts.Connect()
if err != nil {
if logger.V(logger.WarnLevel, logger.DefaultLogger) {
logger.Warnf("Error connecting to broker: %v", err)
}
return err
}
n.conn = c
@@ -196,7 +172,6 @@ func (n *natsBroker) Subscribe(topic string, handler broker.Handler, opts ...bro
n.RUnlock()
opt := broker.SubscribeOptions{
AutoAck: true,
Context: context.Background(),
}
@@ -205,29 +180,25 @@ func (n *natsBroker) Subscribe(topic string, handler broker.Handler, opts ...bro
}
fn := func(msg *nats.Msg) {
var m broker.Message
pub := &publication{t: msg.Subject}
eh := n.opts.ErrorHandler
var m *broker.Message
eh := opt.ErrorHandler
err := n.opts.Codec.Unmarshal(msg.Data, &m)
pub.err = err
pub.m = &m
if err != nil {
m.Body = msg.Data
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Error(err)
}
if eh != nil {
eh(pub)
eh(m, err)
}
return
}
if err := handler(pub); err != nil {
pub.err = err
if err := handler(m); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Error(err)
}
if eh != nil {
eh(pub)
eh(m, err)
}
}
}
@@ -311,7 +282,7 @@ func NewBroker(opts ...broker.Option) broker.Broker {
// Default codec
Codec: json.Marshaler{},
Context: context.Background(),
Registry: registry.DefaultRegistry,
Registry: mdns.NewRegistry(),
}
n := &natsBroker{
@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
"github.com/asim/go-micro/v3/broker"
"github.com/micro/go-micro/v3/broker"
nats "github.com/nats-io/nats.go"
)
@@ -1,7 +1,7 @@
package nats
import (
"github.com/asim/go-micro/v3/broker"
"github.com/micro/go-micro/v3/broker"
nats "github.com/nats-io/nats.go"
)
+8 -22
View File
@@ -4,8 +4,8 @@ import (
"context"
"crypto/tls"
"github.com/asim/go-micro/v3/codec"
"github.com/asim/go-micro/v3/registry"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/registry"
)
type Options struct {
@@ -13,10 +13,6 @@ type Options struct {
Secure bool
Codec codec.Marshaler
// Handler executed when error happens in broker mesage
// processing
ErrorHandler Handler
TLSConfig *tls.Config
// Registry used for clustering
Registry registry.Registry
@@ -32,9 +28,9 @@ type PublishOptions struct {
}
type SubscribeOptions struct {
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
// Handler executed when errors occur processing messages
ErrorHandler ErrorHandler
// Subscribers with the same queue name
// will create a shared subscription where each
// receives a subset of messages.
@@ -59,9 +55,7 @@ func PublishContext(ctx context.Context) PublishOption {
type SubscribeOption func(*SubscribeOptions)
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
opt := SubscribeOptions{
AutoAck: true,
}
opt := SubscribeOptions{}
for _, o := range opts {
o(&opt)
@@ -85,18 +79,10 @@ func Codec(c codec.Marshaler) Option {
}
}
// DisableAutoAck will disable auto acking of messages
// after they have been handled.
func DisableAutoAck() SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = false
}
}
// ErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func ErrorHandler(h Handler) Option {
return func(o *Options) {
func HandleError(h ErrorHandler) SubscribeOption {
return func(o *SubscribeOptions) {
o.ErrorHandler = h
}
}
+32
View File
@@ -0,0 +1,32 @@
// Package build is for building source into a package
package build
// Build is an interface for building packages
type Build interface {
// Package builds a package
Package(name string, src *Source) (*Package, error)
// Remove removes the package
Remove(*Package) error
}
// Source is the source of a build
type Source struct {
// Path to the source if local
Path string
// Language is the language of code
Language string
// Location of the source
Repository string
}
// Package is packaged format for source
type Package struct {
// Name of the package
Name string
// Location of the package
Path string
// Type of package e.g tarball, binary, docker
Type string
// Source of the package
Source *Source
}
@@ -8,18 +8,18 @@ import (
"os"
"path/filepath"
"github.com/asim/go-micro/v3/logger"
"github.com/asim/go-micro/v3/runtime/local/build"
docker "github.com/fsouza/go-dockerclient"
"github.com/micro/go-micro/v3/build"
"github.com/micro/go-micro/v3/logger"
)
type Builder struct {
type dockerBuild struct {
Options build.Options
Client *docker.Client
}
func (d *Builder) Build(s *build.Source) (*build.Package, error) {
image := filepath.Join(s.Repository.Path, s.Repository.Name)
func (d *dockerBuild) Package(name string, s *build.Source) (*build.Package, error) {
image := name
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
@@ -28,7 +28,7 @@ func (d *Builder) Build(s *build.Source) (*build.Package, error) {
dockerFile := "Dockerfile"
// open docker file
f, err := os.Open(filepath.Join(s.Repository.Path, s.Repository.Name, dockerFile))
f, err := os.Open(filepath.Join(s.Path, dockerFile))
if err != nil {
return nil, err
}
@@ -71,12 +71,15 @@ func (d *Builder) Build(s *build.Source) (*build.Package, error) {
}, nil
}
func (d *Builder) Clean(b *build.Package) error {
image := filepath.Join(b.Path, b.Name)
return d.Client.RemoveImage(image)
func (d *dockerBuild) Remove(b *build.Package) error {
return d.Client.RemoveImage(b.Name)
}
func NewBuilder(opts ...build.Option) build.Builder {
func (d *dockerBuild) String() string {
return "docker"
}
func NewBuild(opts ...build.Option) build.Build {
options := build.Options{}
for _, o := range opts {
o(&options)
@@ -86,7 +89,7 @@ func NewBuilder(opts ...build.Option) build.Builder {
if err != nil {
logger.Fatal(err)
}
return &Builder{
return &dockerBuild{
Options: options,
Client: client,
}
@@ -6,10 +6,10 @@ import (
"os/exec"
"path/filepath"
"github.com/asim/go-micro/v3/runtime/local/build"
"github.com/micro/go-micro/v3/build"
)
type Builder struct {
type goBuild struct {
Options build.Options
Cmd string
Path string
@@ -34,35 +34,40 @@ func whichGo() string {
return "go"
}
func (g *Builder) Build(s *build.Source) (*build.Package, error) {
binary := filepath.Join(g.Path, s.Repository.Name)
source := filepath.Join(s.Repository.Path, s.Repository.Name)
func (g *goBuild) Package(name string, src *build.Source) (*build.Package, error) {
binary := filepath.Join(g.Path, name)
source := src.Path
cmd := exec.Command(g.Cmd, "build", "-o", binary, source)
if err := cmd.Run(); err != nil {
return nil, err
}
return &build.Package{
Name: s.Repository.Name,
Name: name,
Path: binary,
Type: "go",
Source: s,
Type: g.String(),
Source: src,
}, nil
}
func (g *Builder) Clean(b *build.Package) error {
func (g *goBuild) Remove(b *build.Package) error {
binary := filepath.Join(b.Path, b.Name)
return os.Remove(binary)
}
func NewBuild(opts ...build.Option) build.Builder {
func (g *goBuild) String() string {
return "golang"
}
func NewBuild(opts ...build.Option) build.Build {
options := build.Options{
Path: os.TempDir(),
}
for _, o := range opts {
o(&options)
}
return &Builder{
return &goBuild{
Options: options,
Cmd: whichGo(),
Path: options.Path,
+45
View File
@@ -0,0 +1,45 @@
// Package tar basically tarballs source code
package tar
import (
"os"
"path/filepath"
"github.com/micro/go-micro/v3/build"
)
type tarBuild struct{}
func (t *tarBuild) Package(name string, src *build.Source) (*build.Package, error) {
pkg := name + ".tar.gz"
// path to the tarball
path := filepath.Join(os.TempDir(), src.Path, pkg)
// create a temp directory
if err := os.MkdirAll(filepath.Join(os.TempDir(), src.Path), 0755); err != nil {
return nil, err
}
if err := Compress(src.Path, path); err != nil {
return nil, err
}
return &build.Package{
Name: name,
Path: path,
Type: t.String(),
Source: src,
}, nil
}
func (t *tarBuild) Remove(b *build.Package) error {
return os.Remove(b.Path)
}
func (t *tarBuild) String() string {
return "tar.gz"
}
func NewBuild(opts ...build.Option) build.Build {
return new(tarBuild)
}
+92
View File
@@ -0,0 +1,92 @@
package tar
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
"os"
"path/filepath"
"strings"
)
func Compress(source, dest string) error {
// tar + gzip
var buf bytes.Buffer
_ = compress(source, &buf)
// write the .tar.gzip
fileToWrite, err := os.OpenFile(dest, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return err
}
_, err = io.Copy(fileToWrite, &buf)
return err
}
func compress(src string, buf io.Writer) error {
// tar > gzip > buf
zr := gzip.NewWriter(buf)
tw := tar.NewWriter(zr)
// walk through every file in the folder
filepath.Walk(src, func(file string, fi os.FileInfo, err error) error {
// generate tar header
header, err := tar.FileInfoHeader(fi, file)
if err != nil {
return err
}
// must provide real name
// (see https://golang.org/src/archive/tar/common.go?#L626)
srcWithSlash := src
if !strings.HasSuffix(src, string(filepath.Separator)) {
srcWithSlash = src + string(filepath.Separator)
}
header.Name = strings.ReplaceAll(file, srcWithSlash, "")
if header.Name == src || len(strings.TrimSpace(header.Name)) == 0 {
return nil
}
// @todo This is a quick hack to speed up whole repo uploads
// https://github.com/micro/micro/pull/956
if !fi.IsDir() && !strings.HasSuffix(header.Name, ".go") &&
!strings.HasSuffix(header.Name, ".mod") &&
!strings.HasSuffix(header.Name, ".sum") {
return nil
}
// write header
if err := tw.WriteHeader(header); err != nil {
return err
}
if fi.IsDir() {
return nil
}
// if not a dir, write file content
data, err := os.Open(file)
if err != nil {
return err
}
if _, err := io.Copy(tw, data); err != nil {
return err
}
return nil
})
// produce tar
if err := tw.Close(); err != nil {
return err
}
// produce gzip
if err := zr.Close(); err != nil {
return err
}
//
return nil
}
+18 -54
View File
@@ -1,65 +1,29 @@
// Package cache is a caching interface
package cache
import (
"context"
"errors"
"time"
)
var (
// DefaultCache is the default cache.
DefaultCache Cache = NewCache()
// DefaultExpiration is the default duration for items stored in
// the cache to expire.
DefaultExpiration time.Duration = 0
// ErrItemExpired is returned in Cache.Get when the item found in the cache
// has expired.
ErrItemExpired error = errors.New("item has expired")
// ErrKeyNotFound is returned in Cache.Get and Cache.Delete when the
// provided key could not be found in cache.
ErrKeyNotFound error = errors.New("key not found in cache")
)
// Cache is the interface that wraps the cache.
//
// Context specifies the context for the cache.
// Get gets a cached value by key.
// Put stores a key-value pair into cache.
// Delete removes a key from cache.
// Cache is an interface for caching
type Cache interface {
Context(ctx context.Context) Cache
Get(key string) (interface{}, time.Time, error)
Put(key string, val interface{}, d time.Duration) error
// Initialise options
Init(...Option) error
// Get a value
Get(key string) (interface{}, error)
// Set a value
Set(key string, val interface{}) error
// Delete a value
Delete(key string) error
// Name of the implementation
String() string
}
// Item represents an item stored in the cache.
type Item struct {
Value interface{}
Expiration int64
type Options struct {
Nodes []string
}
// Expired returns true if the item has expired.
func (i *Item) Expired() bool {
if i.Expiration == 0 {
return false
}
type Option func(o *Options)
return time.Now().UnixNano() > i.Expiration
}
// NewCache returns a new cache.
func NewCache(opts ...Option) Cache {
options := NewOptions(opts...)
items := make(map[string]Item)
if len(options.Items) > 0 {
items = options.Items
}
return &memCache{
opts: options,
items: items,
// Nodes sets the nodes for the cache
func Nodes(v ...string) Option {
return func(o *Options) {
o.Nodes = v
}
}
-111
View File
@@ -1,111 +0,0 @@
package cache
import (
"context"
"testing"
"time"
)
var (
ctx context.Context = context.TODO()
key string = "test"
val interface{} = "hello go-micro"
)
// TestMemCache tests the in-memory cache implementation.
func TestCache(t *testing.T) {
t.Run("CacheGetMiss", func(t *testing.T) {
if _, _, err := NewCache().Context(ctx).Get(key); err == nil {
t.Error("expected to get no value from cache")
}
})
t.Run("CacheGetHit", func(t *testing.T) {
c := NewCache()
if err := c.Context(ctx).Put(key, val, 0); err != nil {
t.Error(err)
}
if a, _, err := c.Context(ctx).Get(key); err != nil {
t.Errorf("Expected a value, got err: %s", err)
} else if a != val {
t.Errorf("Expected '%v', got '%v'", val, a)
}
})
t.Run("CacheGetExpired", func(t *testing.T) {
c := NewCache()
e := 20 * time.Millisecond
if err := c.Context(ctx).Put(key, val, e); err != nil {
t.Error(err)
}
<-time.After(25 * time.Millisecond)
if _, _, err := c.Context(ctx).Get(key); err == nil {
t.Error("expected to get no value from cache")
}
})
t.Run("CacheGetValid", func(t *testing.T) {
c := NewCache()
e := 25 * time.Millisecond
if err := c.Context(ctx).Put(key, val, e); err != nil {
t.Error(err)
}
<-time.After(20 * time.Millisecond)
if _, _, err := c.Context(ctx).Get(key); err != nil {
t.Errorf("expected a value, got err: %s", err)
}
})
t.Run("CacheDeleteMiss", func(t *testing.T) {
if err := NewCache().Context(ctx).Delete(key); err == nil {
t.Error("expected to delete no value from cache")
}
})
t.Run("CacheDeleteHit", func(t *testing.T) {
c := NewCache()
if err := c.Context(ctx).Put(key, val, 0); err != nil {
t.Error(err)
}
if err := c.Context(ctx).Delete(key); err != nil {
t.Errorf("Expected to delete an item, got err: %s", err)
}
if _, _, err := c.Context(ctx).Get(key); err == nil {
t.Errorf("Expected error")
}
})
}
func TestCacheWithOptions(t *testing.T) {
t.Run("CacheWithExpiration", func(t *testing.T) {
c := NewCache(Expiration(20 * time.Millisecond))
if err := c.Context(ctx).Put(key, val, 0); err != nil {
t.Error(err)
}
<-time.After(25 * time.Millisecond)
if _, _, err := c.Context(ctx).Get(key); err == nil {
t.Error("expected to get no value from cache")
}
})
t.Run("CacheWithItems", func(t *testing.T) {
c := NewCache(Items(map[string]Item{key: {val, 0}}))
if a, _, err := c.Context(ctx).Get(key); err != nil {
t.Errorf("Expected a value, got err: %s", err)
} else if a != val {
t.Errorf("Expected '%v', got '%v'", val, a)
}
})
}
-68
View File
@@ -1,68 +0,0 @@
package cache
import (
"context"
"sync"
"time"
)
type memCache struct {
opts Options
sync.RWMutex
ctx context.Context
items map[string]Item
}
func (c *memCache) Context(ctx context.Context) Cache {
c.ctx = ctx
return c
}
func (c *memCache) Get(key string) (interface{}, time.Time, error) {
c.RWMutex.Lock()
defer c.RWMutex.Unlock()
item, found := c.items[key]
if !found {
return nil, time.Time{}, ErrKeyNotFound
}
if item.Expired() {
return nil, time.Time{}, ErrItemExpired
}
return item.Value, time.Unix(0, item.Expiration), nil
}
func (c *memCache) Put(key string, val interface{}, d time.Duration) error {
var e int64
if d == DefaultExpiration {
d = c.opts.Expiration
}
if d > 0 {
e = time.Now().Add(d).UnixNano()
}
c.RWMutex.Lock()
defer c.RWMutex.Unlock()
c.items[key] = Item{
Value: val,
Expiration: e,
}
return nil
}
func (c *memCache) Delete(key string) error {
c.RWMutex.Lock()
defer c.RWMutex.Unlock()
_, found := c.items[key]
if !found {
return ErrKeyNotFound
}
delete(c.items, key)
return nil
}
+80
View File
@@ -0,0 +1,80 @@
// Package memcache is a memcache implementation of the Cache
package memcache
import (
"encoding/json"
"github.com/bradfitz/gomemcache/memcache"
"github.com/micro/go-micro/v3/cache"
)
type memcacheCache struct {
options cache.Options
client *memcache.Client
}
type memcacheItem struct {
Key string
Value interface{}
}
func (m *memcacheCache) Init(opts ...cache.Option) error {
for _, o := range opts {
o(&m.options)
}
return nil
}
func (m *memcacheCache) Get(key string) (interface{}, error) {
item, err := m.client.Get(key)
if err != nil {
return nil, err
}
var mc *memcacheItem
if err := json.Unmarshal(item.Value, &mc); err != nil {
return nil, err
}
return mc.Value, nil
}
func (m *memcacheCache) Set(key string, val interface{}) error {
b, err := json.Marshal(val)
if err != nil {
return err
}
return m.client.Set(&memcache.Item{
Key: key,
Value: b,
})
}
func (m *memcacheCache) Delete(key string) error {
return m.client.Delete(key)
}
func (m *memcacheCache) String() string {
return "memcache"
}
// NewCache returns a new memcache Cache
func NewCache(opts ...cache.Option) cache.Cache {
var options cache.Options
for _, o := range opts {
o(&options)
}
// get and set the nodes
nodes := options.Nodes
if len(nodes) == 0 {
nodes = []string{"localhost:11211"}
}
return &memcacheCache{
options: options,
client: memcache.New(nodes...),
}
}
+56
View File
@@ -0,0 +1,56 @@
// Package memory is an in memory cache
package memory
import (
"sync"
"github.com/micro/go-micro/v3/cache"
"github.com/micro/go-micro/v3/errors"
)
type memoryCache struct {
// TODO: use a decent caching library
sync.RWMutex
values map[string]interface{}
}
func (m *memoryCache) Init(opts ...cache.Option) error {
// TODO: implement
return nil
}
func (m *memoryCache) Get(key string) (interface{}, error) {
m.RLock()
defer m.RUnlock()
v, ok := m.values[key]
if !ok {
return nil, errors.NotFound("go.micro.cache", key+" not found")
}
return v, nil
}
func (m *memoryCache) Set(key string, val interface{}) error {
m.Lock()
m.values[key] = val
m.Unlock()
return nil
}
func (m *memoryCache) Delete(key string) error {
m.Lock()
delete(m.values, key)
m.Unlock()
return nil
}
func (m *memoryCache) String() string {
return "memory"
}
func NewCache(opts ...cache.Option) cache.Cache {
return &memoryCache{
values: make(map[string]interface{}),
}
}
-40
View File
@@ -1,40 +0,0 @@
package cache
import "time"
// Options represents the options for the cache.
type Options struct {
Expiration time.Duration
Items map[string]Item
}
// Option manipulates the Options passed.
type Option func(o *Options)
// Expiration sets the duration for items stored in the cache to expire.
func Expiration(d time.Duration) Option {
return func(o *Options) {
o.Expiration = d
}
}
// Items initializes the cache with preconfigured items.
func Items(i map[string]Item) Option {
return func(o *Options) {
o.Items = i
}
}
// NewOptions returns a new options struct.
func NewOptions(opts ...Option) Options {
options := Options{
Expiration: DefaultExpiration,
Items: make(map[string]Item),
}
for _, o := range opts {
o(&options)
}
return options
}
-43
View File
@@ -1,43 +0,0 @@
package cache
import (
"testing"
"time"
)
func TestOptions(t *testing.T) {
testData := map[string]struct {
set bool
expiration time.Duration
items map[string]Item
}{
"DefaultOptions": {false, DefaultExpiration, map[string]Item{}},
"ModifiedOptions": {true, time.Second, map[string]Item{"test": {"hello go-micro", 0}}},
}
for k, d := range testData {
t.Run(k, func(t *testing.T) {
var opts Options
if d.set {
opts = NewOptions(
Expiration(d.expiration),
Items(d.items),
)
} else {
opts = NewOptions()
}
// test options
for _, o := range []Options{opts} {
if o.Expiration != d.expiration {
t.Fatalf("Expected expiration '%v', got '%v'", d.expiration, o.Expiration)
}
if o.Items["test"] != d.items["test"] {
t.Fatalf("Expected items %#v, got %#v", d.items, o.Items)
}
}
})
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/asim/go-micro/v3/util/backoff"
"github.com/micro/go-micro/v3/util/backoff"
)
type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error)
+67 -2
View File
@@ -4,6 +4,8 @@ import (
"context"
"testing"
"time"
"github.com/micro/go-micro/v3/codec"
)
func TestBackoff(t *testing.T) {
@@ -16,10 +18,13 @@ func TestBackoff(t *testing.T) {
7900 * time.Millisecond,
}
c := NewClient()
r := &testRequest{
service: "test",
method: "test",
}
for i := 0; i < 5; i++ {
d, err := exponentialBackoff(context.TODO(), c.NewRequest("test", "test", nil), i)
d, err := exponentialBackoff(context.TODO(), r, i)
if err != nil {
t.Fatal(err)
}
@@ -29,3 +34,63 @@ func TestBackoff(t *testing.T) {
}
}
}
type testRequest struct {
service string
method string
endpoint string
contentType string
codec codec.Codec
body interface{}
opts RequestOptions
}
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
var opts RequestOptions
for _, o := range reqOpts {
o(&opts)
}
// set the content-type specified
if len(opts.ContentType) > 0 {
contentType = opts.ContentType
}
return &testRequest{
service: service,
method: endpoint,
endpoint: endpoint,
body: request,
contentType: contentType,
opts: opts,
}
}
func (r *testRequest) ContentType() string {
return r.contentType
}
func (r *testRequest) Service() string {
return r.service
}
func (r *testRequest) Method() string {
return r.method
}
func (r *testRequest) Endpoint() string {
return r.endpoint
}
func (r *testRequest) Body() interface{} {
return r.body
}
func (r *testRequest) Codec() codec.Writer {
return r.codec
}
func (r *testRequest) Stream() bool {
return r.opts.Stream
}
-66
View File
@@ -1,66 +0,0 @@
package client
import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"time"
"github.com/asim/go-micro/v3/metadata"
cache "github.com/patrickmn/go-cache"
)
// NewCache returns an initialised cache.
func NewCache() *Cache {
return &Cache{
cache: cache.New(cache.NoExpiration, 30*time.Second),
}
}
// Cache for responses
type Cache struct {
cache *cache.Cache
}
// Get a response from the cache
func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) {
return c.cache.Get(key(ctx, req))
}
// Set a response in the cache
func (c *Cache) Set(ctx context.Context, req *Request, rsp interface{}, expiry time.Duration) {
c.cache.Set(key(ctx, req), rsp, expiry)
}
// List the key value pairs in the cache
func (c *Cache) List() map[string]string {
items := c.cache.Items()
rsp := make(map[string]string, len(items))
for k, v := range items {
bytes, _ := json.Marshal(v.Object)
rsp[k] = string(bytes)
}
return rsp
}
// key returns a hash for the context and request
func key(ctx context.Context, req *Request) string {
ns, _ := metadata.Get(ctx, "Micro-Namespace")
bytes, _ := json.Marshal(map[string]interface{}{
"namespace": ns,
"request": map[string]interface{}{
"service": (*req).Service(),
"endpoint": (*req).Endpoint(),
"method": (*req).Method(),
"body": (*req).Body(),
},
})
h := fnv.New64()
h.Write(bytes)
return fmt.Sprintf("%x", h.Sum(nil))
}
-76
View File
@@ -1,76 +0,0 @@
package client
import (
"context"
"testing"
"time"
"github.com/asim/go-micro/v3/metadata"
)
func TestCache(t *testing.T) {
ctx := context.TODO()
req := NewRequest("go.micro.service.foo", "Foo.Bar", nil)
t.Run("CacheMiss", func(t *testing.T) {
if _, ok := NewCache().Get(ctx, &req); ok {
t.Errorf("Expected to get no result from Get")
}
})
t.Run("CacheHit", func(t *testing.T) {
c := NewCache()
rsp := "theresponse"
c.Set(ctx, &req, rsp, time.Minute)
if res, ok := c.Get(ctx, &req); !ok {
t.Errorf("Expected a result, got nothing")
} else if res != rsp {
t.Errorf("Expected '%v' result, got '%v'", rsp, res)
}
})
}
func TestCacheKey(t *testing.T) {
ctx := context.TODO()
req1 := NewRequest("go.micro.service.foo", "Foo.Bar", nil)
req2 := NewRequest("go.micro.service.foo", "Foo.Baz", nil)
req3 := NewRequest("go.micro.service.foo", "Foo.Baz", "customquery")
t.Run("IdenticalRequests", func(t *testing.T) {
key1 := key(ctx, &req1)
key2 := key(ctx, &req1)
if key1 != key2 {
t.Errorf("Expected the keys to match for identical requests and context")
}
})
t.Run("DifferentRequestEndpoints", func(t *testing.T) {
key1 := key(ctx, &req1)
key2 := key(ctx, &req2)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different request endpoints")
}
})
t.Run("DifferentRequestBody", func(t *testing.T) {
key1 := key(ctx, &req2)
key2 := key(ctx, &req3)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different request bodies")
}
})
t.Run("DifferentMetadata", func(t *testing.T) {
mdCtx := metadata.Set(context.TODO(), "Micro-Namespace", "bar")
key1 := key(mdCtx, &req1)
key2 := key(ctx, &req1)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different metadata")
}
})
}
+2 -44
View File
@@ -5,7 +5,7 @@ import (
"context"
"time"
"github.com/asim/go-micro/v3/codec"
"github.com/micro/go-micro/v3/codec"
)
// Client is the interface used to make requests to services.
@@ -22,11 +22,6 @@ type Client interface {
String() string
}
// Router manages request routing
type Router interface {
SendRequest(context.Context, Request) (Response, error)
}
// Message is the interface for publishing asynchronously
type Message interface {
Topic() string
@@ -62,7 +57,7 @@ type Response interface {
Read() ([]byte, error)
}
// Stream is the inteface for a bidirectional synchronous stream
// Stream is the interface for a bidirectional synchronous stream
type Stream interface {
// Context for the stream
Context() context.Context
@@ -96,8 +91,6 @@ type MessageOption func(*MessageOptions)
type RequestOption func(*RequestOptions)
var (
// DefaultClient is a default client to use out of the box
DefaultClient Client = newRpcClient()
// DefaultBackoff is the default backoff function for retries
DefaultBackoff = exponentialBackoff
// DefaultRetry is the default check-for-retry function for retries
@@ -110,39 +103,4 @@ var (
DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl
DefaultPoolTTL = time.Minute
// NewClient returns a new client
NewClient func(...Option) Client = newRpcClient
)
// Makes a synchronous call to a service using the default client
func Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
return DefaultClient.Call(ctx, request, response, opts...)
}
// Publishes a publication using the default client. Using the underlying broker
// set within the options.
func Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
return DefaultClient.Publish(ctx, msg, opts...)
}
// Creates a new message using the default client
func NewMessage(topic string, payload interface{}, opts ...MessageOption) Message {
return DefaultClient.NewMessage(topic, payload, opts...)
}
// Creates a new request using the default client. Content Type will
// be set to the default within options and use the appropriate codec
func NewRequest(service, endpoint string, request interface{}, reqOpts ...RequestOption) Request {
return DefaultClient.NewRequest(service, endpoint, request, reqOpts...)
}
// Creates a streaming connection with a service and returns responses on the
// channel passed in. It's up to the user to close the streamer.
func NewStream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
return DefaultClient.Stream(ctx, request, opts...)
}
func String() string {
return DefaultClient.String()
}
-16
View File
@@ -1,16 +0,0 @@
package client
import (
"context"
)
type clientKey struct{}
func FromContext(ctx context.Context) (Client, bool) {
c, ok := ctx.Value(clientKey{}).(Client)
return c, ok
}
func NewContext(ctx context.Context, c Client) context.Context {
return context.WithValue(ctx, clientKey{}, c)
}
@@ -9,8 +9,8 @@ import (
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/asim/go-micro/v3/codec"
"github.com/asim/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/codec/bytes"
"github.com/oxtoacart/bpool"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
+39
View File
@@ -0,0 +1,39 @@
package grpc
import (
"github.com/micro/go-micro/v3/errors"
"google.golang.org/grpc/status"
)
func microError(err error) error {
// no error
switch err {
case nil:
return nil
}
if verr, ok := err.(*errors.Error); ok {
return verr
}
// grpc error
s, ok := status.FromError(err)
if !ok {
return err
}
// return first error from details
if details := s.Details(); len(details) > 0 {
if verr, ok := details[0].(error); ok {
return microError(verr)
}
}
// try to decode micro *errors.Error
if e := errors.Parse(s.Message()); e.Code > 0 {
return e // actually a micro error
}
// fallback
return errors.InternalServerError("go.micro.client", s.Message())
}
@@ -11,15 +11,11 @@ import (
"sync/atomic"
"time"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/cmd"
"github.com/asim/go-micro/v3/client"
raw "github.com/asim/go-micro/v3/codec/bytes"
"github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/metadata"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/selector"
pnet "github.com/asim/go-micro/v3/util/net"
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/client"
raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/metadata"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@@ -34,8 +30,6 @@ type grpcClient struct {
}
func init() {
cmd.DefaultClients["grpc"] = NewClient
encoding.RegisterCodec(wrapCodec{jsonCodec{}})
encoding.RegisterCodec(wrapCodec{protoCodec{}})
encoding.RegisterCodec(wrapCodec{bytesCodec{}})
@@ -75,35 +69,9 @@ func (g *grpcClient) secure(addr string) grpc.DialOption {
return grpc.WithInsecure()
}
func (g *grpcClient) next(request client.Request, opts client.CallOptions) (selector.Next, error) {
service, address, _ := pnet.Proxy(request.Service(), opts.Address)
// return remote address
if len(address) > 0 {
return func() (*registry.Node, error) {
return &registry.Node{
Address: address[0],
}, nil
}, nil
}
// get next nodes from the selector
next, err := g.opts.Selector.Select(service, opts.SelectOptions...)
if err != nil {
if err == selector.ErrNotFound {
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error())
}
return next, nil
}
func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
func (g *grpcClient) call(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
var header map[string]string
address := node.Address
header = make(map[string]string)
if md, ok := metadata.FromContext(ctx); ok {
header = make(map[string]string, len(md))
@@ -134,7 +102,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
grpcDialOptions := []grpc.DialOption{
grpc.WithTimeout(opts.DialTimeout),
g.secure(address),
g.secure(addr),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
grpc.MaxCallSendMsgSize(maxSendMsgSize),
@@ -145,13 +113,13 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
grpcDialOptions = append(grpcDialOptions, opts...)
}
cc, err := g.pool.getConn(address, grpcDialOptions...)
cc, err := g.pool.getConn(addr, grpcDialOptions...)
if err != nil {
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
}
defer func() {
// defer execution of release
g.pool.release(address, cc, grr)
g.pool.release(addr, cc, grr)
}()
ch := make(chan error, 1)
@@ -177,11 +145,9 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
return grr
}
func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
func (g *grpcClient) stream(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
var header map[string]string
address := node.Address
if md, ok := metadata.FromContext(ctx); ok {
header = make(map[string]string, len(md))
for k, v := range md {
@@ -219,14 +185,14 @@ func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client
grpcDialOptions := []grpc.DialOption{
grpc.WithTimeout(opts.DialTimeout),
g.secure(address),
g.secure(addr),
}
if opts := g.getGrpcDialOptions(); opts != nil {
grpcDialOptions = append(grpcDialOptions, opts...)
}
cc, err := grpc.DialContext(dialCtx, address, grpcDialOptions...)
cc, err := grpc.DialContext(dialCtx, addr, grpcDialOptions...)
if err != nil {
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
}
@@ -271,15 +237,15 @@ func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client
// setup the stream response
stream := &grpcStream{
context: ctx,
request: req,
ClientStream: st,
context: ctx,
request: req,
response: &response{
conn: cc,
stream: st,
codec: cf,
gcodec: codec,
},
stream: st,
conn: cc,
cancel: cancel,
}
@@ -393,11 +359,6 @@ func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface
opt(&callOpts)
}
next, err := g.next(req, callOpts)
if err != nil {
return err
}
// check if we already have a deadline
d, ok := ctx.Deadline()
if !ok {
@@ -427,6 +388,34 @@ func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface
gcall = callOpts.CallWrappers[i-1](gcall)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = g.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = g.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(g.opts.Proxy) > 0 {
callOpts.Address = []string{g.opts.Proxy}
}
// lookup the route to send the reques to
// TODO apply any filtering here
routes, err := g.opts.Lookup(ctx, req, callOpts)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err := callOpts.Selector.Select(routes)
if err != nil {
return err
}
// return errors.New("go.micro.client", "request timeout", 408)
call := func(i int) error {
// call backoff first. Someone may want an initial start delay
@@ -440,19 +429,16 @@ func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface
time.Sleep(t)
}
// select next node
node, err := next()
service := req.Service()
if err != nil {
if err == selector.ErrNotFound {
return errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error())
}
// get the next node
node := next()
// make the call
err = gcall(ctx, node, req, rsp, callOpts)
g.opts.Selector.Mark(service, node, err)
// record the result of the call to inform future routing decisions
g.opts.Selector.Record(node, err)
// try and transform the error to a go-micro error
if verr, ok := err.(*errors.Error); ok {
return verr
}
@@ -500,11 +486,6 @@ func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...cli
opt(&callOpts)
}
next, err := g.next(req, callOpts)
if err != nil {
return nil, err
}
// #200 - streams shouldn't have a request timeout set on the context
// should we noop right here?
@@ -522,6 +503,34 @@ func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...cli
gstream = callOpts.CallWrappers[i-1](gstream)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = g.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = g.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(g.opts.Proxy) > 0 {
callOpts.Address = []string{g.opts.Proxy}
}
// lookup the route to send the reques to
// TODO: move to internal lookup func
routes, err := g.opts.Lookup(ctx, req, callOpts)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err := callOpts.Selector.Select(routes)
if err != nil {
return nil, err
}
call := func(i int) (client.Stream, error) {
// call backoff first. Someone may want an initial start delay
t, err := callOpts.Backoff(ctx, req, i)
@@ -534,20 +543,22 @@ func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...cli
time.Sleep(t)
}
node, err := next()
service := req.Service()
if err != nil {
if err == selector.ErrNotFound {
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error())
}
// get the next node
node := next()
// make the call
stream := &grpcStream{}
err = g.stream(ctx, node, req, stream, callOpts)
g.opts.Selector.Mark(service, node, err)
// record the result of the call to inform future routing decisions
g.opts.Selector.Record(node, err)
// try and transform the error to a go-micro error
if verr, ok := err.(*errors.Error); ok {
return nil, verr
}
g.opts.Selector.Record(node, err)
return stream, err
}
@@ -574,7 +585,7 @@ func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...cli
return rsp.stream, nil
}
retry, rerr := callOpts.Retry(ctx, req, i, err)
retry, rerr := callOpts.Retry(ctx, req, i, grr)
if rerr != nil {
return nil, rerr
}
@@ -592,6 +603,16 @@ func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...cli
func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
var options client.PublishOptions
var body []byte
// fail early on connect error
if !g.once.Load().(bool) {
if err := g.opts.Broker.Connect(); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
g.once.Store(true)
}
for _, o := range opts {
o(&options)
}
@@ -603,17 +624,15 @@ func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...clie
md["Content-Type"] = p.ContentType()
md["Micro-Topic"] = p.Topic()
cf, err := g.newGRPCCodec(p.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
var body []byte
// passed in raw data
if d, ok := p.Payload().(*raw.Frame); ok {
body = d.Data
} else {
// use codec for payload
cf, err := g.newGRPCCodec(p.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// set the body
b, err := cf.Marshal(p.Payload())
if err != nil {
@@ -622,13 +641,6 @@ func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...clie
body = b
}
if !g.once.Load().(bool) {
if err = g.opts.Broker.Connect(); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
g.once.Store(true)
}
topic := p.Topic()
// get the exchange
@@ -5,19 +5,18 @@ import (
"net"
"testing"
"github.com/asim/go-micro/plugins/registry/memory/v3"
"github.com/asim/go-micro/v3/client"
"github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/selector"
"github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/memory"
"github.com/micro/go-micro/v3/router"
regRouter "github.com/micro/go-micro/v3/router/registry"
pgrpc "google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
// server is used to implement helloworld.GreeterServer.
type greeterServer struct {
pb.UnimplementedGreeterServer
}
type greeterServer struct{}
// SayHello implements helloworld.GreeterServer
func (g *greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
@@ -58,16 +57,11 @@ func TestGRPCClient(t *testing.T) {
},
})
// create selector
se := selector.NewSelector(
selector.Registry(r),
)
// create router
rtr := regRouter.NewRouter(router.Registry(r))
// create client
c := NewClient(
client.Registry(r),
client.Selector(se),
)
c := NewClient(client.Router(rtr))
testMethods := []string{
"/helloworld.Greeter/SayHello",
@@ -1,7 +1,7 @@
package grpc
import (
"github.com/asim/go-micro/v3/client"
"github.com/micro/go-micro/v3/client"
)
type grpcEvent struct {
@@ -5,7 +5,7 @@ import (
"context"
"crypto/tls"
"github.com/asim/go-micro/v3/client"
"github.com/micro/go-micro/v3/client"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
)
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"github.com/asim/go-micro/v3/client"
"github.com/asim/go-micro/v3/codec"
"github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/codec"
)
type grpcRequest struct {
@@ -3,8 +3,8 @@ package grpc
import (
"strings"
"github.com/asim/go-micro/v3/codec"
"github.com/asim/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/codec/bytes"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
)
@@ -5,17 +5,19 @@ import (
"io"
"sync"
"github.com/asim/go-micro/v3/client"
"github.com/micro/go-micro/v3/client"
"google.golang.org/grpc"
)
// Implements the streamer interface
type grpcStream struct {
// embed so we can access if need be
grpc.ClientStream
sync.RWMutex
closed bool
err error
conn *grpc.ClientConn
stream grpc.ClientStream
request client.Request
response client.Response
context context.Context
@@ -35,7 +37,7 @@ func (g *grpcStream) Response() client.Response {
}
func (g *grpcStream) Send(msg interface{}) error {
if err := g.stream.SendMsg(msg); err != nil {
if err := g.ClientStream.SendMsg(msg); err != nil {
g.setError(err)
return err
}
@@ -44,7 +46,8 @@ func (g *grpcStream) Send(msg interface{}) error {
func (g *grpcStream) Recv(msg interface{}) (err error) {
defer g.setError(err)
if err = g.stream.RecvMsg(msg); err != nil {
if err = g.ClientStream.RecvMsg(msg); err != nil {
// #202 - inconsistent gRPC stream behavior
// the only way to tell if the stream is done is when we get a EOF on the Recv
// here we should close the underlying gRPC ClientConn
@@ -52,7 +55,10 @@ func (g *grpcStream) Recv(msg interface{}) (err error) {
if err == io.EOF && closeErr != nil {
err = closeErr
}
return err
}
return
}
@@ -83,6 +89,6 @@ func (g *grpcStream) Close() error {
// cancel the context
g.cancel()
g.closed = true
g.stream.CloseSend()
g.ClientStream.CloseSend()
return g.conn.Close()
}
+50
View File
@@ -0,0 +1,50 @@
package client
import (
"context"
"sort"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/router"
)
// LookupFunc is used to lookup routes for a service
type LookupFunc func(context.Context, Request, CallOptions) ([]string, error)
// LookupRoute for a request using the router and then choose one using the selector
func LookupRoute(ctx context.Context, req Request, opts CallOptions) ([]string, error) {
// check to see if an address was provided as a call option
if len(opts.Address) > 0 {
return opts.Address, nil
}
// construct the router query
query := []router.LookupOption{}
// if a custom network was requested, pass this to the router. By default the router will use it's
// own network, which is set during initialisation.
if len(opts.Network) > 0 {
query = append(query, router.LookupNetwork(opts.Network))
}
// lookup the routes which can be used to execute the request
routes, err := opts.Router.Lookup(req.Service(), query...)
if err == router.ErrRouteNotFound {
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", req.Service(), err.Error())
} else if err != nil {
return nil, errors.InternalServerError("go.micro.client", "error getting next %s node: %s", req.Service(), err.Error())
}
// sort by lowest metric first
sort.Slice(routes, func(i, j int) bool {
return routes[i].Metric < routes[j].Metric
})
var addrs []string
for _, route := range routes {
addrs = append(addrs, route.Address)
}
return addrs, nil
}
@@ -1,7 +1,7 @@
package client
package mucp
import (
"github.com/asim/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry"
)
var (
+111 -127
View File
@@ -1,4 +1,5 @@
package client
// Package mucp provides a transport agnostic RPC client
package mucp
import (
"context"
@@ -6,29 +7,28 @@ import (
"sync/atomic"
"time"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/codec"
raw "github.com/asim/go-micro/v3/codec/bytes"
"github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/metadata"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/selector"
"github.com/asim/go-micro/v3/transport"
"github.com/asim/go-micro/v3/util/buf"
"github.com/asim/go-micro/v3/util/net"
"github.com/asim/go-micro/v3/util/pool"
"github.com/google/uuid"
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/codec"
raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v3/network/transport"
"github.com/micro/go-micro/v3/util/buf"
"github.com/micro/go-micro/v3/util/pool"
)
type rpcClient struct {
seq uint64
once atomic.Value
opts Options
opts client.Options
pool pool.Pool
seq uint64
}
func newRpcClient(opt ...Option) Client {
opts := NewOptions(opt...)
// NewClient returns a new micro client interface
func NewClient(opt ...client.Option) client.Client {
opts := client.NewOptions(opt...)
p := pool.NewPool(
pool.Size(opts.PoolSize),
@@ -43,7 +43,7 @@ func newRpcClient(opt ...Option) Client {
}
rc.once.Store(false)
c := Client(rc)
c := client.Client(rc)
// wrap in reverse
for i := len(opts.Wrappers); i > 0; i-- {
@@ -63,9 +63,7 @@ func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}
func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error {
address := node.Address
func (r *rpcClient) call(ctx context.Context, addr string, req client.Request, resp interface{}, opts client.CallOptions) error {
msg := &transport.Message{
Header: make(map[string]string),
}
@@ -89,16 +87,9 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
// set the accept header
msg.Header["Accept"] = req.ContentType()
// setup old protocol
cf := setupProtocol(msg, node)
// no codec specified
if cf == nil {
var err error
cf, err = r.newCodec(req.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
cf, err := r.newCodec(req.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
dOpts := []transport.DialOption{
@@ -109,7 +100,7 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
c, err := r.pool.Get(address, dOpts...)
c, err := r.pool.Get(addr, dOpts...)
if err != nil {
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
@@ -182,9 +173,7 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
return nil
}
func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) {
address := node.Address
func (r *rpcClient) stream(ctx context.Context, addr string, req client.Request, opts client.CallOptions) (client.Stream, error) {
msg := &transport.Message{
Header: make(map[string]string),
}
@@ -205,16 +194,9 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
// set the accept header
msg.Header["Accept"] = req.ContentType()
// set old codecs
cf := setupProtocol(msg, node)
// no codec specified
if cf == nil {
var err error
cf, err = r.newCodec(req.ContentType())
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
cf, err := r.newCodec(req.ContentType())
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
dOpts := []transport.DialOption{
@@ -225,7 +207,7 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
c, err := r.opts.Transport.Dial(address, dOpts...)
c, err := r.opts.Transport.Dial(addr, dOpts...)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
@@ -292,7 +274,7 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
return stream, nil
}
func (r *rpcClient) Init(opts ...Option) error {
func (r *rpcClient) Init(opts ...client.Option) error {
size := r.opts.PoolSize
ttl := r.opts.PoolTTL
tr := r.opts.Transport
@@ -316,62 +298,19 @@ func (r *rpcClient) Init(opts ...Option) error {
return nil
}
func (r *rpcClient) Options() Options {
func (r *rpcClient) Options() client.Options {
return r.opts
}
// next returns an iterator for the next nodes to call
func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, error) {
// try get the proxy
service, address, _ := net.Proxy(request.Service(), opts.Address)
// return remote address
if len(address) > 0 {
nodes := make([]*registry.Node, len(address))
for i, addr := range address {
nodes[i] = &registry.Node{
Address: addr,
// Set the protocol
Metadata: map[string]string{
"protocol": "mucp",
},
}
}
// crude return method
return func() (*registry.Node, error) {
return nodes[time.Now().Unix()%int64(len(nodes))], nil
}, nil
}
// get next nodes from the selector
next, err := r.opts.Selector.Select(service, opts.SelectOptions...)
if err != nil {
if err == selector.ErrNotFound {
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error())
}
return next, nil
}
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
func (r *rpcClient) Call(ctx context.Context, request client.Request, response interface{}, opts ...client.CallOption) error {
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
next, err := r.next(request, callOpts)
if err != nil {
return err
}
// check if we already have a deadline
d, ok := ctx.Deadline()
if !ok {
if d, ok := ctx.Deadline(); !ok {
// no deadline so we create a new one
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
@@ -379,8 +318,8 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
opt := WithRequestTimeout(time.Until(d))
opt(&callOpts)
remaining := d.Sub(time.Now())
client.WithRequestTimeout(remaining)(&callOpts)
}
// should we noop right here?
@@ -398,6 +337,34 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
rcall = callOpts.CallWrappers[i-1](rcall)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = r.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = r.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(r.opts.Proxy) > 0 {
callOpts.Address = []string{r.opts.Proxy}
}
// lookup the route to send the reques to
// TODO apply any filtering here
routes, err := r.opts.Lookup(ctx, request, callOpts)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err := callOpts.Selector.Select(routes)
if err != nil {
return err
}
// return errors.New("go.micro.client", "request timeout", 408)
call := func(i int) error {
// call backoff first. Someone may want an initial start delay
@@ -411,19 +378,15 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
time.Sleep(t)
}
// select next node
node, err := next()
service := request.Service()
if err != nil {
if err == selector.ErrNotFound {
return errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
}
// get the next node
node := next()
// make the call
err = rcall(ctx, node, request, response, callOpts)
r.opts.Selector.Mark(service, node, err)
// record the result of the call to inform future routing decisions
r.opts.Selector.Record(node, err)
return err
}
@@ -431,7 +394,7 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
retries := callOpts.Retries
// disable retries when using a proxy
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
if len(r.opts.Proxy) > 0 {
retries = 0
}
@@ -468,18 +431,13 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
return gerr
}
func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
func (r *rpcClient) Stream(ctx context.Context, request client.Request, opts ...client.CallOption) (client.Stream, error) {
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
next, err := r.next(request, callOpts)
if err != nil {
return nil, err
}
// should we noop right here?
select {
case <-ctx.Done():
@@ -487,7 +445,35 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
default:
}
call := func(i int) (Stream, error) {
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = r.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = r.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(r.opts.Proxy) > 0 {
callOpts.Address = []string{r.opts.Proxy}
}
// lookup the route to send the reques to
// TODO apply any filtering here
routes, err := r.opts.Lookup(ctx, request, callOpts)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err := callOpts.Selector.Select(routes)
if err != nil {
return nil, err
}
call := func(i int) (client.Stream, error) {
// call backoff first. Someone may want an initial start delay
t, err := callOpts.Backoff(ctx, request, i)
if err != nil {
@@ -499,22 +485,20 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
time.Sleep(t)
}
node, err := next()
service := request.Service()
if err != nil {
if err == selector.ErrNotFound {
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return nil, errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
}
// get the next node
node := next()
// perform the call
stream, err := r.stream(ctx, node, request, callOpts)
r.opts.Selector.Mark(service, node, err)
// record the result of the call to inform future routing decisions
r.opts.Selector.Record(node, err)
return stream, err
}
type response struct {
stream Stream
stream client.Stream
err error
}
@@ -522,7 +506,7 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
retries := callOpts.Retries
// disable retries when using a proxy
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
if len(r.opts.Proxy) > 0 {
retries = 0
}
@@ -560,8 +544,8 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
return nil, grr
}
func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
options := PublishOptions{
func (r *rpcClient) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
options := client.PublishOptions{
Context: context.Background(),
}
for _, o := range opts {
@@ -629,11 +613,11 @@ func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOpt
}, broker.PublishContext(options.Context))
}
func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...MessageOption) Message {
func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...client.MessageOption) client.Message {
return newMessage(topic, message, r.opts.ContentType, opts...)
}
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...client.RequestOption) client.Request {
return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
}
@@ -1,19 +1,19 @@
package client
package mucp
import (
"bytes"
errs "errors"
"github.com/asim/go-micro/v3/codec"
raw "github.com/asim/go-micro/v3/codec/bytes"
"github.com/asim/go-micro/v3/codec/grpc"
"github.com/asim/go-micro/v3/codec/json"
"github.com/asim/go-micro/v3/codec/jsonrpc"
"github.com/asim/go-micro/v3/codec/proto"
"github.com/asim/go-micro/v3/codec/protorpc"
"github.com/asim/go-micro/v3/errors"
"github.com/asim/go-micro/v3/registry"
"github.com/asim/go-micro/v3/transport"
"github.com/micro/go-micro/v3/codec"
raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/codec/grpc"
"github.com/micro/go-micro/v3/codec/json"
"github.com/micro/go-micro/v3/codec/jsonrpc"
"github.com/micro/go-micro/v3/codec/proto"
"github.com/micro/go-micro/v3/codec/protorpc"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/network/transport"
"github.com/micro/go-micro/v3/registry"
)
const (
@@ -129,10 +129,8 @@ func setHeaders(m *codec.Message, stream string) {
// setupProtocol sets up the old protocol
func setupProtocol(msg *transport.Message, node *registry.Node) codec.NewCodec {
protocol := node.Metadata["protocol"]
// got protocol
if len(protocol) > 0 {
// get the protocol from node metadata
if protocol := node.Metadata["protocol"]; len(protocol) > 0 {
return nil
}

Some files were not shown because too many files have changed in this diff Show More