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 versionGet a Züs Wallet
You’ll need a .json wallet file.
It is typically located at:
~/.zcn/wallet.jsonAlternatively, 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.jsonCreate 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.go3. Code Your main.go
main.gopackage 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/muxIn case you see an error while importing go_zus_handlers, run this command:
go mod init go_zus_blog4. Build Your Handlers
sdk_handler.go
sdk_handler.gopackage 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.gopackage 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.gopackage 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.gopackage 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.goExpected output:
Go microservice running on port 80806. Test With curl
curlInitialize SDK
curl -X POST http://localhost:8080/init-sdkUpload 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