GO SDK Microservices
This guide provides a complete walkthrough for setting up and running a Go-based microservice that interacts with the Züs decentralized storage network.
1. Prerequisites Setup
Before you begin:
Install Go
install Go (version 1.20+ recommended). Verify installation:
go version
Get a Züs Wallet
You’ll need a .json
wallet file.
It is typically located at:
~/.zcn/wallet.json
Alternatively, you can store the wallet JSON file in any location of your choice. To retrieve your wallet information, you can use the recover wallet command from zwalletcli
.
Or you can use any location to create the json file. You can retrieve your wallet info by using recover wallet from the zwalletcli.
Below is sample wallet structure:
{
"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"client_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"keys": [
{
"public_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"private_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
],
"mnemonics": "xxxx xxxx xxxx xxxxx",
"version": "1.0",
"date_created": "2023-05-03T12:44:46+05:30",
"nonce": 0
}
Create config.json
config.json
Create a file named config.json
in your project root with the following structure:
{
"wallet_json": "/Users/yourusername/.zcn/wallet.json",
"block_worker": "https://mainnet.zus.network/dns",
"chain_id": "0af33bc5402b5f13e349347db8b88b6543157baadb7c9265ef0a8f87429e4a44",
"signature_scheme": "bls0chain",
"min_submit": 50,
"min_confirmation": 50,
"confirmation_chain_length": 3,
"sharder_consensous": 3
}
2. Project File Structure
Your project should be structured as follows:
/project-root
├── config.json
├── main.go
└── handlers/
├── download_handler.go
├── memfile_handler.go
├── sdk_handler.go
└── upload_handler.go
3. Code Your main.go
main.go
package main
import (
"log"
"net/http"
"go_zus_blog/handlers" // Replace with your module name if different
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/init-sdk", handlers.InitSDKHandler).Methods("POST")
router.HandleFunc("/upload-file", handlers.UploadFileHandler).Methods("POST")
router.HandleFunc("/download-file", handlers.DownloadFileHandler).Methods("POST")
log.Println("Go microservice running on port 8080")
log.Fatal(http.ListenAndServe(":8080", router))
}
Install Gorilla Mux:
go get -u github.com/gorilla/mux
In case you see an error while importing go_zus_handlers, run this command:
go mod init go_zus_blog
4. Build Your Handlers
sdk_handler.go
sdk_handler.go
package handlers
import (
"encoding/json"
"log"
"net/http"
"os"
"github.com/0chain/gosdk/core/client"
)
type Config struct {
WalletJSON string `json:"wallet_json"`
BlockWorker string `json:"block_worker"`
ChainID string `json:"chain_id"`
SignatureScheme string `json:"signature_scheme"`
MinSubmit int `json:"min_submit"`
MinConfirmation int `json:"min_confirmation"`
ConfirmationChainLength int `json:"confirmation_chain_length"`
SharderConsensous int `json:"sharder_consensous"`
}
func InitSDKHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
file, err := os.ReadFile("./config.json")
if err != nil {
http.Error(w, "Failed to read config.json: "+err.Error(), http.StatusInternalServerError)
return
}
var config Config
err = json.Unmarshal(file, &config)
if err != nil {
http.Error(w, "Invalid config.json: "+err.Error(), http.StatusInternalServerError)
return
}
if config.WalletJSON == "" {
http.Error(w, "Missing wallet JSON path", http.StatusBadRequest)
return
}
if _, err := os.Stat(config.WalletJSON); os.IsNotExist(err) {
http.Error(w, "Wallet JSON file not found: "+config.WalletJSON, http.StatusBadRequest)
return
}
walletContent, err := os.ReadFile(config.WalletJSON)
if err != nil {
http.Error(w, "Failed to read wallet JSON: "+err.Error(), http.StatusInternalServerError)
return
}
err = client.InitSDK(
string(walletContent),
config.BlockWorker,
config.ChainID,
config.SignatureScheme,
0,
true,
0,
config.MinSubmit,
config.MinConfirmation,
config.ConfirmationChainLength,
config.SharderConsensous,
)
if err != nil {
http.Error(w, "SDK initialization failed: "+err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "SDK initialized successfully"}`))
}
memfile_handler.go
memfile_handler.go
package handlers
import (
"bytes"
"errors"
"io"
"os"
"sync"
"time"
)
type MemFile struct {
buffer *bytes.Buffer
mu sync.Mutex
name string
modTime time.Time
mode os.FileMode
}
func NewMemFile(name string) *MemFile {
return &MemFile{
buffer: new(bytes.Buffer),
name: name,
modTime: time.Now(),
mode: 0644,
}
}
func (m *MemFile) Read(p []byte) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.buffer.Read(p)
}
func (m *MemFile) Write(p []byte) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.buffer.Write(p)
}
func (m *MemFile) Seek(offset int64, whence int) (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
switch whence {
case io.SeekStart:
m.buffer = bytes.NewBuffer(m.buffer.Bytes()[offset:])
return offset, nil
case io.SeekEnd:
m.buffer = bytes.NewBuffer(m.buffer.Bytes()[len(m.buffer.Bytes())+int(offset):])
return int64(len(m.buffer.Bytes())), nil
case io.SeekCurrent:
return 0, errors.New("unsupported seek method")
}
return 0, errors.New("invalid seek whence")
}
func (m *MemFile) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
m.buffer.Reset()
return nil
}
func (m *MemFile) Stat() (os.FileInfo, error) {
return &memFileInfo{
name: m.name,
size: int64(m.buffer.Len()),
modTime: m.modTime,
mode: m.mode,
}, nil
}
func (m *MemFile) Sync() error {
return nil
}
func (m *MemFile) ToBytes() []byte {
return m.buffer.Bytes()
}
type memFileInfo struct {
name string
size int64
modTime time.Time
mode os.FileMode
}
func (fi *memFileInfo) Name() string { return fi.name }
func (fi *memFileInfo) Size() int64 { return fi.size }
func (fi *memFileInfo) Mode() os.FileMode { return fi.mode }
func (fi *memFileInfo) ModTime() time.Time { return fi.modTime }
func (fi *memFileInfo) IsDir() bool { return false }
func (fi *memFileInfo) Sys() interface{} { return nil }
upload_handler.go
upload_handler.go
package handlers
import (
"encoding/json"
"log"
"net/http"
"os"
"path/filepath"
"github.com/0chain/gosdk/constants"
"github.com/0chain/gosdk/zboxcore/fileref"
"github.com/0chain/gosdk/zboxcore/sdk"
)
type ShortURLResponse struct {
AuthTicket string `json:"auth_ticket"`
}
type UploadRequest struct {
AllocationID string `json:"allocation_id"`
LocalPath string `json:"local_path"`
Encrypt bool `json:"encrypt"`
WebStreaming bool `json:"web_streaming"`
IsMemoryDownload bool `json:"download_file"`
}
func UploadFileHandler(w http.ResponseWriter, r *http.Request) {
var req UploadRequest
// Decode the incoming JSON payload
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
log.Printf("Invalid request payload: %v", err)
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
log.Printf("Received upload request: %+v", req)
// Validate required fields
if req.AllocationID == "" || req.LocalPath == "" {
log.Printf("Missing required fields: %+v", req)
http.Error(w, "AllocationID and LocalPath are required", http.StatusBadRequest)
return
}
// Check if the file exists locally
if _, err := os.Stat(req.LocalPath); os.IsNotExist(err) {
log.Printf("File not found at: %s", req.LocalPath)
http.Error(w, "File not found at "+req.LocalPath, http.StatusBadRequest)
return
}
// Generate the remote path using the file name
fileName := filepath.Base(req.LocalPath)
remotePath := "/" + fileName
log.Printf("Generated remote path: %s", remotePath)
// Get the allocation object using the provided AllocationID
allocation, err := sdk.GetAllocation(req.AllocationID)
if err != nil {
log.Printf("Failed to get allocation: %v", err)
http.Error(w, "Failed to get allocation: "+err.Error(), http.StatusInternalServerError)
return
}
// Open the local file
fileReader, err := os.Open(req.LocalPath)
if err != nil {
log.Printf("Failed to open file: %v", err)
http.Error(w, "Failed to open file: "+err.Error(), http.StatusInternalServerError)
return
}
defer fileReader.Close()
// Get file size
fileInfo, err := fileReader.Stat()
if err != nil {
log.Printf("Failed to get file info: %v", err)
http.Error(w, "Failed to get file info: "+err.Error(), http.StatusInternalServerError)
return
}
// Set up the operation for the upload
operations := []sdk.OperationRequest{
{
OperationType: constants.FileOperationInsert,
FileMeta: sdk.FileMeta{
Path: req.LocalPath,
RemotePath: remotePath,
RemoteName: fileName,
ActualSize: fileInfo.Size(),
},
FileReader: fileReader,
IsWebstreaming: req.WebStreaming,
IsRepair: false,
Opts: func() []sdk.ChunkedUploadOption {
// Include encryption option only if Encrypt is true
if req.Encrypt {
return []sdk.ChunkedUploadOption{
sdk.WithEncrypt(req.Encrypt),
}
}
return nil
}(),
},
}
// Perform the multi-operation
log.Println("Starting DoMultiOperation...")
err = allocation.DoMultiOperation(operations)
if err != nil {
log.Printf("Multi-operation failed: %v", err)
http.Error(w, "Upload failed: "+err.Error(), http.StatusInternalServerError)
return
}
log.Println("File uploaded successfully to Züs network")
// Generate Auth Ticket
//authTicket, err := allocation.GetAuthTicketForShare(remotePath, req.FileName, fileref.FILE, "")
authTicket, err := allocation.GetAuthTicketForShare(remotePath, fileName, fileref.FILE, "")
if err != nil {
log.Printf("Failed to generate auth ticket: %v", err)
http.Error(w, "Failed to generate auth ticket", http.StatusInternalServerError)
return
}
log.Printf("Auth Ticket: %+v", authTicket)
// Return short URL
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(ShortURLResponse{
AuthTicket: authTicket,
})
}
download_handler.go
download_handler.go
package handlers
import (
"encoding/json"
"log"
"net/http"
//"path/filepath"
//"strings"
"github.com/0chain/gosdk/zboxcore/fileref"
"github.com/0chain/gosdk/zboxcore/sdk"
)
type DownloadRequest struct {
AllocationID string `json:"allocation_id"`
FileName string `json:"filename"`
AuthTicket string `json:"auth_ticket"`
LocalPath string `json:"local_path"`
}
func DownloadFileHandler(w http.ResponseWriter, r *http.Request) {
var req DownloadRequest
// Decode the incoming JSON payload
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
log.Printf("Invalid request payload: %v", err)
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
log.Printf("Received download request: %+v", req)
allocation, err := sdk.GetAllocation(req.AllocationID)
if err != nil {
http.Error(w, "Failed to get allocation", http.StatusInternalServerError)
return
}
// Generate the remote path using the file name
remotePath := "/" + req.FileName
log.Printf("Generated remote path: %s", remotePath)
remoteLookupHash := fileref.GetReferenceLookup(req.AllocationID, remotePath)
log.Printf("Generated remoteLookupHash: %s", remoteLookupHash)
// Create in-memory file
//memFile := NewMemFile(remotePath)
// Download file
err = allocation.DownloadFromAuthTicket(
req.LocalPath, req.AuthTicket, remoteLookupHash, remotePath, false, nil, true,
)
if err != nil {
http.Error(w, "File download failed", http.StatusInternalServerError)
return
}
5. Run Your Server
go run main.go
Expected output:
Go microservice running on port 8080
6. Test With curl
curl
Initialize SDK
curl -X POST http://localhost:8080/init-sdk
Upload a File
curl -X POST http://localhost:8080/upload-file \
-H "Content-Type: application/json" \
-d '{
"allocation_id": "your_allocation_id",
"local_path": "/Users/you/Downloads/file.txt",
"encrypt": false,
"web_streaming": false,
"download_file": false
}'
Download a File
curl -X POST http://localhost:8080/download-file \
-H "Content-Type: application/json" \
-d '{
"allocation_id": "allocation-id",
"filename": "filename of file you would like to download from allocation",
"auth_ticket": "auth-ticket-string",
"local_path": "/path/to/local/download"
}'
Last updated