Skip to content

Commit

Permalink
add Unix domain socket support (dapr#206)
Browse files Browse the repository at this point in the history
* add Unix domain socket support

Signed-off-by: Long <[email protected]>

* update examples

Signed-off-by: Long <[email protected]>
  • Loading branch information
daixiang0 authored Jan 13, 2022
1 parent d3c3384 commit f912500
Show file tree
Hide file tree
Showing 7 changed files with 1,896 additions and 0 deletions.
19 changes: 19 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package client

import (
"context"
"fmt"
"log"
"net"
"os"
Expand Down Expand Up @@ -159,6 +160,7 @@ type Client interface {
// NewClientWithPort(port string) (client Client, err error)
// NewClientWithAddress(address string) (client Client, err error)
// NewClientWithConnection(conn *grpc.ClientConn) Client
// NewClientWithSocket(socket string) (client Client, err error)
func NewClient() (client Client, err error) {
port := os.Getenv(daprPortEnvVarName)
if port == "" {
Expand Down Expand Up @@ -207,6 +209,23 @@ func NewClientWithAddress(address string) (client Client, err error) {
return newClientWithConnectionAndCancelFunc(conn, ctxCancel), nil
}

// NewClientWithSocket instantiates Dapr using specific socket.
func NewClientWithSocket(socket string) (client Client, err error) {
if socket == "" {
return nil, errors.New("nil socket")
}
logger.Printf("dapr client initializing for: %s", socket)
addr := fmt.Sprintf("unix://%s", socket)
conn, err := grpc.Dial(addr, grpc.WithInsecure())
if err != nil {
return nil, errors.Wrapf(err, "error creating connection to '%s': %v", addr, err)
}
if hasToken := os.Getenv(apiTokenEnvVarName); hasToken != "" {
logger.Println("client uses API token")
}
return NewClientWithConnection(conn), nil
}

// NewClientWithConnection instantiates Dapr client using specific connection.
func NewClientWithConnection(conn *grpc.ClientConn) Client {
return newClientWithConnectionAndCancelFunc(conn, func() {})
Expand Down
77 changes: 77 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

const (
testBufSize = 1024 * 1024
testSocket = "/tmp/dapr.socket"
)

var testClient Client
Expand All @@ -44,6 +45,15 @@ func TestMain(m *testing.M) {
testClient = c
r := m.Run()
f()

if r != 0 {
os.Exit(r)
}

c, f = getTestClientWithSocket(ctx)
testClient = c
r = m.Run()
f()
os.Exit(r)
}

Expand All @@ -63,13 +73,49 @@ func TestNewClient(t *testing.T) {
assert.Error(t, err)
})

t.Run("no arg with socket", func(t *testing.T) {
_, err := NewClientWithSocket("")
assert.Error(t, err)
})

t.Run("new client closed with token", func(t *testing.T) {
t.Setenv(apiTokenEnvVarName, "test")
c, err := NewClientWithSocket(testSocket)
assert.NoError(t, err)
defer c.Close()
c.WithAuthToken("")
})

t.Run("new client closed with empty token", func(t *testing.T) {
testClient.WithAuthToken("")
})

t.Run("new client with trace ID", func(t *testing.T) {
_ = testClient.WithTraceID(context.Background(), "test")
})

t.Run("new socket client closed with token", func(t *testing.T) {
t.Setenv(apiTokenEnvVarName, "test")
c, err := NewClientWithSocket(testSocket)
assert.NoError(t, err)
defer c.Close()
c.WithAuthToken("")
})

t.Run("new socket client closed with empty token", func(t *testing.T) {
c, err := NewClientWithSocket(testSocket)
assert.NoError(t, err)
defer c.Close()
c.WithAuthToken("")
})

t.Run("new socket client with trace ID", func(t *testing.T) {
c, err := NewClientWithSocket(testSocket)
assert.NoError(t, err)
defer c.Close()
ctx := c.WithTraceID(context.Background(), "")
_ = c.WithTraceID(ctx, "test")
})
}

func TestShutdown(t *testing.T) {
Expand Down Expand Up @@ -112,6 +158,37 @@ func getTestClient(ctx context.Context) (client Client, closer func()) {
return
}

func getTestClientWithSocket(ctx context.Context) (client Client, closer func()) {
s := grpc.NewServer()
pb.RegisterDaprServer(s, &testDaprServer{
state: make(map[string][]byte),
})

var lc net.ListenConfig
l, err := lc.Listen(ctx, "unix", testSocket)
if err != nil {
logger.Fatalf("socket test server created with error: %v", err)
}

go func() {
if err = s.Serve(l); err != nil && err.Error() != "accept unix /tmp/dapr.socket: use of closed network connection" {
logger.Fatalf("socket test server exited with error: %v", err)
}
}()

closer = func() {
l.Close()
s.Stop()
os.Remove(testSocket)
}

if client, err = NewClientWithSocket(testSocket); err != nil {
logger.Fatalf("socket test client created with error: %v", err)
}

return
}

type testDaprServer struct {
pb.UnimplementedDaprServer
state map[string][]byte
Expand Down
16 changes: 16 additions & 0 deletions examples/socket/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.PHONY: bin
bin: go-mod
go build -o order order.go

.PHONY: go-mod
go-mod: go-check
go mod tidy
go mod vendor

.PHONY: go-check
go-check:
@which go > /dev/null

.PHONY: clean
clean:
rm -f ./order
131 changes: 131 additions & 0 deletions examples/socket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Hello World with Unix domain socket

This tutorial will demonstrate how to instrument your application with Dapr, and run it locally on your machine.
You will deploying advanced `order` applications with [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) based on [Hello World](https://github.com/dapr/go-sdk/tree/main/examples/hello-world).

There is a great performance imporvement With Unix domain socket, please notice that it does not support on Windows.

## Prerequisites
This quickstart requires you to have the following installed on your machine:
- [Docker](https://docs.docker.com/)
- [Go](https://golang.org/)

## Step 1 - Setup Dapr

Follow [instructions](https://docs.dapr.io/getting-started/install-dapr/) to download and install the Dapr CLI and initialize Dapr.

## Step 2 - Understand the code

The [order.go](./order.go) is a simple command line application, that implements four commands:
* `put` sends an order with configurable order ID.
* `get` return the current order number.
* `del` deletes the order.
* `seq` streams a sequence of orders with incrementing order IDs.

First, the app instantiates Dapr client:

```go
client, err := dapr.NewClientWithSocket(socket)
if err != nil {
panic(err)
}
defer client.Close()
```

Then, depending on the command line argument, the app invokes corresponding method:

Persist the state:
```go
err := client.SaveState(ctx, stateStoreName, "order", []byte(strconv.Itoa(orderID)))
```
Retrieve the state:
```go
item, err := client.GetState(ctx, stateStoreName, "order")
```
Delete the state:
```go
err := client.DeleteState(ctx, stateStoreName, "order")
```

## Step 3 - Run the app with Dapr

1. Build the app

<!-- STEP
name: Build the app
-->

```bash
make
```

<!-- END_STEP -->

2. Run the app

There are two ways to launch Dapr applications. You can pass the app executable to the Dapr runtime:

<!-- STEP
name: Run and send order
background: true
sleep: 5
expected_stdout_lines:
- '== APP == dapr client initializing for: /tmp/dapr-order-app-grpc.socket'
- '== APP == Sending order ID 20'
- '== APP == Successfully persisted state'
-->

```bash
dapr run --app-id order-app --log-level error --unix-domain-socket /tmp -- ./order put --id 20
```

<!-- END_STEP -->

<!-- STEP
name: Run and get order
background: true
sleep: 5
expected_stdout_lines:
- '== APP == dapr client initializing for: /tmp/dapr-order-app-grpc.socket'
- '== APP == Getting order'
- '== APP == Order ID 20'
-->

```bash
dapr run --app-id order-app --log-level error --unix-domain-socket /tmp ./order get
```

<!-- END_STEP -->

Alternatively, you can start a standalone Dapr runtime, and call the app from another shell:

```bash
dapr run --app-id order-app --log-level error --unix-domain-socket /tmp
```


```bash
./order put --id 10

./order get
```

To terminate your services, simply stop the "dapr run" process, or use the Dapr CLI "stop" command:

```bash
dapr stop --app-id order-app
```


3. Run multiple apps

You can run more than one app in Dapr runtime. In this example you will call `order seq` which sends a sequence of orders.
Another instance of the `order` app will read the state.

```sh
dapr run --app-id order-app --log-level error --unix-domain-socket /tmp ./order seq
```

```sh
./order get
```
24 changes: 24 additions & 0 deletions examples/socket/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module github.com/dapr/go-sdk/examples/socket

go 1.17

require (
github.com/dapr/go-sdk v1.2.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)

replace github.com/dapr/go-sdk => ../../

require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a // indirect
github.com/dapr/dapr v1.4.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20210524171403-669157292da3 // indirect
google.golang.org/grpc v1.38.0 // indirect
google.golang.org/protobuf v1.26.0 // indirect
)
Loading

0 comments on commit f912500

Please sign in to comment.