Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

go/mio: add an example of REST API server #2111

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions bindings/go/go_motr_api_server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

## Motr REST API server

Motr REST API Server is a simple REST API Server which based on motr go bindings.

It included a REST API server and a http client.

The API implemented as below:

```
kv/put
kv/get
kv/delete
object/read
object/write
```

#### Server

To start REST API server, first you need to run motr server.

Follow motr [quick start guide](/doc/Quick-Start-Guide.rst) to build motr.

To start motr in development mode, open a new terminal, go to `%cortx_motr_dir%/motr/examples` at `motr` root directory.

```sh
cd %cortx_home%/motr/examples
./setup_a_running_motr_system.sh
```

After motr server start, you should see the connection information at the end of terminal.

At this directory, open `motr.toml`, this is where `motr` connection configure, modify the value accordingly and save the file.

Open another terminal and run the REST API server :
```
> go mod tidy
> export MOTR_HOME=motr_root_diretory
> CGO_CFLAGS="-I/$MOTR_HOME -I/usr/include/motr -DM0_EXTERN=extern -DM0_INTERNAL= -Wno-attributes" CGO_LDFLAGS="-L$MOTR_HOME/motr/.libs -Wl,-rpath=$MOTR_HOME/motr/.libs -lmotr" go run .
```

You should see the server start and running at port `8081`.


#### Client

To interect with the REST API server, you could use http client under `http_client` folder:

```go
client := NewClient("localhost", 8081)

// read from file and write to motr
response, err := client.Write(IndexID, "black_hole.png")

// read from motr and write to file
err := client.Read(IndexID, objectSize, "out.png")
```

Open another terminal and run the tests to see if the http client work as expected.
```
> cd http_client
> go test -v
```

You could refer to test cases for more sample of client usage.


108 changes: 108 additions & 0 deletions bindings/go/go_motr_api_server/api/api_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package api

import (
"errors"
"fmt"
"io/ioutil"
"net/url"
"strconv"
"time"

"github.com/gin-gonic/gin"
)

func get(ctx *gin.Context, name string, required bool, defaultValue interface{}, errs *[]error) interface{} {

var val string

if ctx.Request.Method == "POST" {
val = ctx.PostForm(name)

// if POST binary content, get from query
if val == "" {
val = ctx.Query(name)
}

} else {
val = ctx.Query(name)
}

if required && val == "" {
toError(errs, name+" is required")
return defaultValue
} else if val == "" {
return defaultValue
}

val, err := url.QueryUnescape(val)

if err != nil {
toError(errs, err.Error())
}

return val
}

func getString(ctx *gin.Context, name string, required bool, defaultValue interface{}, errs *[]error) string {
val := get(ctx, name, required, defaultValue, errs)
return toString(val)
}

func getInt(ctx *gin.Context, name string, required bool, defaultValue interface{}, errs *[]error) int {
val := get(ctx, name, required, defaultValue, errs)
c := toI32(val)
return int(c)
}

func getBytes(ctx *gin.Context, errs *[]error) []byte {
ByteBody, err := ioutil.ReadAll(ctx.Request.Body)

if err != nil {
toError(errs, err.Error())
return nil
}

return ByteBody
}

func toError(errs *[]error, msg string) {
*errs = append(*errs, errors.New(msg))
}

func hasError(errs []error) error {

for _, err := range errs {
if err != nil {
return err
}
}

return nil
}

func createError(err error) map[string]interface{} {

errs := make(map[string]interface{})
errs["error"] = err.Error()

return errs
}

func toTime(end time.Duration) string {
return fmt.Sprintf("%v", end)
}

func toString(t interface{}) string {
return fmt.Sprintf("%+v", t)
}

func toI32(t interface{}) int32 {
s := toString(t)
i, err := strconv.Atoi(s)

if err != nil {
return 0
}

return int32(i)
}
125 changes: 125 additions & 0 deletions bindings/go/go_motr_api_server/api/kv_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package api

import (
"fmt"
"go_motr_api_server/v2/motr"
"net/http"
"time"

"github.com/gin-gonic/gin"
)

func Status(ctx *gin.Context) {

json := make(map[string]interface{})
json["status"] = "ok"

ctx.JSON(http.StatusOK, json)
}

func GetKey(ctx *gin.Context) {

start := time.Now()

errs := &[]error{}

indexID := getString(ctx, "index_id", true, nil, errs)
key := getString(ctx, "key", true, nil, errs)

if err := hasError(*errs); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

value, err := motr.Get(indexID, key)

if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

end := time.Since(start)

meta := make(map[string]interface{})
meta["time"] = toTime(end)

json := make(map[string]interface{})
json["meta"] = meta
json["index_id"] = indexID
json["key"] = key
json["value"] = value

ctx.JSON(http.StatusOK, json)
}

func DeleteKey(ctx *gin.Context) {

start := time.Now()

errs := &[]error{}

indexID := getString(ctx, "index_id", true, nil, errs)
key := getString(ctx, "key", true, nil, errs)

if err := hasError(*errs); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

err := motr.Delete(indexID, key)

if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

end := time.Since(start)

meta := make(map[string]interface{})
meta["time"] = toTime(end)

json := make(map[string]interface{})
json["meta"] = meta
json["index_id"] = indexID
json["key"] = key

ctx.JSON(http.StatusOK, json)
}

func PutKeyValue(ctx *gin.Context) {

start := time.Now()

errs := &[]error{}

indexID := getString(ctx, "index_id", true, nil, errs)
key := getString(ctx, "key", true, nil, errs)
value := getString(ctx, "value", true, nil, errs)

if err := hasError(*errs); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

fmt.Println(indexID, key, value)

err := motr.Put(indexID, key, value)

if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

end := time.Since(start)

meta := make(map[string]interface{})
meta["time"] = toTime(end)

json := make(map[string]interface{})
json["meta"] = meta
json["index_id"] = indexID
json["key"] = key
json["value"] = value

ctx.JSON(http.StatusOK, json)
}
72 changes: 72 additions & 0 deletions bindings/go/go_motr_api_server/api/object_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package api

import (
"bytes"
"go_motr_api_server/v2/motr"
"net/http"
"time"

"github.com/gin-gonic/gin"
)

func Write(ctx *gin.Context) {

start := time.Now()

errs := &[]error{}

indexID := getString(ctx, "index_id", true, nil, errs)
size := getInt(ctx, "size", true, nil, errs)
byteBody := getBytes(ctx, errs)

if err := hasError(*errs); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

err := motr.Write("", indexID, uint64(size), bytes.NewReader(byteBody))

if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

end := time.Since(start)

meta := make(map[string]interface{})
meta["time"] = toTime(end)

json := make(map[string]interface{})
json["meta"] = meta
json["indexID"] = indexID
json["size"] = size

ctx.JSON(http.StatusOK, json)
}

func Read(ctx *gin.Context) {

start := time.Now()

errs := &[]error{}

indexID := getString(ctx, "index_id", true, nil, errs)
size := getInt(ctx, "size", true, nil, errs)

if err := hasError(*errs); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

byteContent, err := motr.Read("", indexID, uint64(size))

if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err))
return
}

end := time.Since(start)

ctx.Header("time", toTime(end))
ctx.Data(http.StatusOK, "application/octet-stream", byteContent)
}
Loading