中间件HTTPAPI说明文档

MEMO 中间件为开发者、企业等提供一个安全、灵活、具有可组合性的数据网络。用户可灵活选择适合自己的底层存储系统。

中间件会提供存储单价查询、存储套餐购买服务,用户选择充值并购买套餐,获得存储空间,之后就可以上传下载文件,并且支持用户查询上传文件列表。

启动中间件服务,默认监听端口为8080; 本文档所使用的例子中,http监听端口设为8081,baseURL为http://localhost:8081;以下所有请求URL应根据实际情况进行更改。

1. 登陆验证

1.1 获取挑战信息

登录前,需要根据地址获取challenge message,并且必须通过Origin字段设置domain

请求URL:http://localhost:8081/challenge?address={address}

请求方式:GET

返回参数:text信息(EIP-4361定义的格式)

请求示例:

challenge

注意事项:调用challenge接口时,需要在headers的Origin字段中指定域名,目前仅支持域名http://memo.io,否则会返回错误。

错误码:

HTTP状态码 错误码 错误描述
500 InternalError We encountered an internal error, please try again.

1.2 登录请求(使用eth账户登录)

用户可使用eth账号进行登录。该登录方式需要用户先调用challenge接口获取信息,随后,用户利用私钥对该信息进行签名,签名方式在EIP-191中定义。注意:在调用challenge接口获取挑战信息后,需要在30s内完成登录,否则登录失败。

请求URL:http://localhost:8081/login

请求方式:POST

请求参数(JSON格式):

{
"message":
"memo.io wants you to sign in with your Ethereum account:\n0xFD976F1F3dC6413Da5Fed05471eaBB01F4FaaC42\n\n\nURI: http://memo.io\nVersion: 1\nChain ID: 985\nNonce: 12c2ad59e12abbe224cf86741c4bf00a21432fb2673b29a694b72062385f9b5d\nIssued At: 2023-04-23T07:36:10Z",
"signature":
"0xd5c406dd9ca168cc0894788cd262c3e9bd2f5413f87654c8cb685a9d872b9ab151fe80930c8de4da6adeadf38714387bee65c949ba47efa3eb5ee1335b6cc79400"
}
参数名 变量 类型【长度限制】 必填 描述
message 请求签名信息 string 调用lens的challenge接口获取需要签名的text信息
signature 签名 string text签名后的信息

获取签名信息的方式如下述代码所示:

package main

import (
"crypto/ecdsa"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)

func main() {
secretKey := flag.String("sk", "", "the sk to signature")

flag.Parse()

privateKey, err := crypto.HexToECDSA(*secretKey)
if err != nil {
    fmt.Println(err.Error())
    return
}

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
    log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
}
address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()

// get MEMO-Middleware challenge message
text, err := Challenge(address)
if err != nil {
    log.Fatal(err)
}
fmt.Println("message:", text)

// eip191-signature
hash := crypto.Keccak256([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(text), text)))
signature, err := crypto.Sign(hash, privateKey)
if err != nil {
    log.Fatal(err)
}
sig := hexutil.Encode(signature)
fmt.Println("login sig:\n", sig)
}

func Challenge(address string) (string, error) {
client := &http.Client{Timeout: time.Minute}
// ip:port should be corresponding to that MEMO-Middleware server is listening
url := "http://localhost:8081/challenge"

req, err := http.NewRequest("GET", url, nil)
if err != nil {
    return "", err
}

params := req.URL.Query()
params.Add("address", address)
req.URL.RawQuery = params.Encode()
req.Header.Set("Origin", "https://memo.io")

res, err := client.Do(req)
if err != nil {
    return "", err
}
defer res.Body.Close()

body, err := ioutil.ReadAll(res.Body)
if err != nil {
    return "", err
}

if res.StatusCode != http.StatusOK {
    return "", fmt.Errorf("respond code[%d]: %s", res.StatusCode, string(body))
}

return string(body), nil
}

返回参数(JSON):

{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoxLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc3NDkwMTgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.F0asDvu3LH3ccK6LAztBGF1TTzGw7Stc9gBEzVicuE4",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoyLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc4MDk0MDgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.PDxQ2orOlsES6fvkyR-xWc6M1yBY8RiFTcn8m5AGROc"
}
参数名 变量 类型【长度限制】 必填 描述
accessToken 认证令牌 string 15分钟内,持有该令牌可以免密认证
refreshToken 刷新令牌 string 7天内,持有该令牌可以重新生成认证令牌

请求示例:

login

错误码:

HTTP状态码 错误码 错误描述
500 InternalError We encountered an internal error, please try again.
401 Authentication There is an empty parameter;Can't parse message;Got wrong chain id; Got wrong domain; Got wrong nonce; Got wrong address; Got wrong signature

1.3 登录请求(使用lens账户登录)

使用lens账户登录,无需获取1.1中的挑战信息,但需要调用lens的challenge接口获取需要签名的text信息(EIP-4361定义的格式),同时利用EIP-191定义的签名方式对text信息进行签名,需在30s内发出登录请求。运行中间件服务时,将开启或关闭检查账户是否是Lens账户。

请求URL:http://localhost:8081/lens/login

请求方式:POST

请求参数(JSON格式):

{
"message":"\nmemo.io wants you to sign in with your Ethereum account:\n0x51632235cc673a788E02B30B9F16F7B1D300194C\n\nSign in with ethereum to lens\n\nURI: memo.io\nVersion: 1\nChain ID: 137\nNonce: bcb9b92754e2b900\nIssued At: 2023-03-14T07:26:05.501Z\n ",
"signature":"0x..."
}
参数名 变量 类型 必填 描述
message 请求签名信息 string 调用lens的challenge接口获取需要签名的text信息
signature 签名 string text签名后的信息

调用lens接口,获取text信息的方式如下:

import(
"context"

"github.com/machinebox/graphql"
)

type Challenge struct {
Challenge struct {
    Text string
} `graphql:"challenge(request: $request)"`
}

type ChallengeRequest struct {
Address string `json:"address"`
}

func ChallengeRequest(address string) (string, error) {
client := graphql.NewClient("https://api.lens.dev")

req := graphql.NewRequest(`
    query Challenge($request:ChallengeRequest!) {
        challenge(request:$request) {
            text
        }
    }`)

req.Var("request", ChallengeRequest{ Address: address })
req.Header.Set("Origin", "memo.io")

var query Challenge
if err := client.Run(context.Background(), req, &query); err != nil {
    return "", err
}

return query.Challenge.Text, nil
}

使用EIP-191定义的方式签名可以借鉴1.2中的代码示例。

返回参数(JSON):

{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoxLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc3NDkwMTgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.F0asDvu3LH3ccK6LAztBGF1TTzGw7Stc9gBEzVicuE4",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoyLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc4MDk0MDgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.PDxQ2orOlsES6fvkyR-xWc6M1yBY8RiFTcn8m5AGROc",
"isRegistered": false
}
参数名 变量 类型【长度限制】 必填 描述
accessToken 认证令牌 string 15分钟内,持有该令牌可以免密认证
refreshToken 刷新令牌 string 7天内,持有该令牌可以重新生成认证令牌
isRegistered 是否注册过Lens bool 账户是否已经在Lens中注册

请求示例:

lenslogin

错误码:

HTTP状态码 错误码 错误描述
517 Address The address {address} is not registered on lens
500 InternalError We encountered an internal error, please try again.
401 Authentication There is an empty parameter; Got wrong domain; Got wrong chain id; Got wrong address/signature;

1.4 刷新accessToken

accessToken的有效期为15分钟;refreshToken的有效期为7天,当accesToken过期后,需要根据refreshToken刷新accessToken进行免密认证登录。

请求URL:http://localhost:8081/refresh

请求方式:GET

请求头信息:

参数名 变量 类型 必填 描述
Authorization "Bearer 刷新令牌" string 上述登录请求返回的refreshToken

返回参数(JSON):

参数名 变量 类型【长度限制】 描述
accessToken 认证令牌 string 有效期过后重新生成的认证令牌

请求示例:

refresh

错误码:

HTTP状态码 错误码 错误描述
401 Unauthorized Illegal refresh token

 

2. 文件上传

用户登录后,可上传文件。MEFS采用对象存储,文件默认上传至与登录账户地址同名的bucket中,账户上传文件时,若还未创建同名bucket,中间件则会自动帮用户创建同名bucket.

上传文件受用户的充值金额及存储空间限制,用户可先查询存储单价和存储套餐,进行充值,从而得到存储空间和余额。

请求URL:

选择上传至mefs:http://localhost:8081/mefs/

选择上传至ipfs:http://localhost:8081/ipfs/

请求方式:POST

请求头信息:

参数名 变量
Content-Type multipart/form-data
Authorization "Bearer 登录验证产生的access token"

请求参数:

参数名 变量 类型【长度限制】 必填 描述
file 待上传的文件 File 注意选择File格式

返回参数(JSON):

参数名 变量 类型【长度限制】 必填 描述
cid 上传文件CID string 文件唯一标识符

返回例子:

Response:Status 200

{
 "cid": "bafybeie2ph7iokrckc5iy6xu7npa4xqst6ez5ewb7j2igeilxjaw6sd2qi"
}

请求示例:

put1

put2

错误码:

HTTP状态码 错误码 错误描述
401 Authentication Token is Null; Invalid token payload; Invalid token;
500 InternalError We encountered an internal error, please try again.
518 Storage storage not support

 

3. 文件下载

用户可以根据文件的cid下载相应的文件。

请求URL:

http://ip:port/mefs/$cid;

http://ip:port/ipfs/$cid;

选择从mefs下载:http://localhost:8081/mefs/bafkreifzwcj6vkozz6brwutpxl3hqneran4y5vtvirnbrtw3l2m3jtlgq4

选择从ipfs下载:http://localhost:8081/ipfs/bafkreifzwcj6vkozz6brwutpxl3hqneran4y5vtvirnbrtw3l2m3jtlgq4

请求方式:GET

请求头信息:

请求参数:

返回参数(DataFromReader):

返回文件。

参数名 描述
code 状态码 200
contentLength 文件大小
contentType 文件类型
reader io.Reader,文件传输缓冲区

请求示例:

get1

get2

错误码:

HTTP状态码 错误码 错误描述
500 InternalError We encountered an internal error, please try again.
518 Storage storage not support
517 Address address is null

 

4. 文件删除

删除上传的文件。仅支持MEFS类型存储的删除功能。

请求URL:
http://ip:port/mefs/delete

请求方式:GET

请求头信息:

参数名 变量 类型【长度限制】 必填 描述
Authorization "Bearer 登录验证产生的accessToken" string 若过期,可通过刷新accessToken来获得新的有效accessToken

请求参数:

参数名 变量 类型【长度限制】 必填 描述
mid 文件mid string 文件唯一标识符,上传文件时返回的字符串

返回参数(JSON):

参数名 类型【长度限制】 描述
Status string 删除成功或失败

请求示例:

delete

 

5. 文件列表查询

用户查询自己所上传的文件列表。

请求URL:

http://ip:port/mefs/listobjects

http://ip:port/ipfs/listobjects

请求方式:GET

将列出登录账户的文件列表。

请求头信息:

参数名 变量 类型【长度限制】 必填 描述
Authorization "Bearer 登录验证产生的accessToken" string 若过期,可通过刷新accessToken来获得新的有效accessToken

请求参数:

返回参数(JSON):

参数名 类型【长度限制】 描述
Address string 账户以太坊钱包地址
Storage string mefs或者ipfs
Object struct 文件列表

每个文件包含信息:

参数名 类型 描述
Name string 文件名
Size int64 文件大小
Cid string 文件cid
ModTime time 文件修改时间
UserDefined struct 关于文件的其他一些信息

UserDefined结构体包含信息:

参数名 类型 描述
encryption string 文件加密方式
etag string 文件ID模式(默认cid)

请求示例:

list

错误码:

HTTP状态码 错误码 错误描述
401 Authentication Token is Null; Invalid token payload; Invalid token;
516 Storage list object error %s
518 Storage storage not support

 

6. 查询账户余额

用户查询自己的存储余额。

请求URL:http://localhost:8081/account/balance

请求方式:GET

请求头信息:

参数名 变量 类型【长度限制】 必填 描述
Authorization "Bearer 登录验证产生的accessToken" string 若过期,可通过刷新accessToken来获得新的有效accessToken

请求参数:

返回参数(JSON):

参数名 类型【长度限制】 描述
Address string 登录账户的以太坊钱包地址
Balance string 余额的最小单位数字表示

请求示例:

错误码:

HTTP状态码 错误码 错误描述
401 Authentication Token is Null; Invalid token payload; Invalid token;
516 Storage make bucket error %s;
518 Storage storage not support
520 Eth rpc error

 

7. 查询存储空间

用户查询自己的存储空间,包括已用、可用、免费空间,以及上传文件数。

请求URL:http://localhost:8081/account/getstorage?stype={stype}

请求方式:GET

请求头信息:

参数名 变量 类型【长度限制】 必填 描述
Authorization "Bearer 登录验证产生的accessToken" string 若过期,可通过刷新accessToken来获得新的有效accessToken

请求参数:

参数名 变量 描述
stype 存储类型 目前支持可查询的存储类型有mefs,ipfs,qiniu

返回参数(JSON):

参数名 类型【长度限制】 描述
Used string 已使用空间
Available string 可用空间
Free string 免费空间
Files string 文件数

请求示例:

getstorage

错误码:

HTTP状态码 错误码 错误描述
401 Authentication Token is Null; Invalid token payload; Invalid token;Authentication Failed (InValid token type)

 

8. 单价查询

查询各种存储方式的存储单价。

服务端暂未实现。

 

9. 套餐查询

查询多种存储方式的存储套餐。

请求URL:http://localhost:8081/account/pkginfos

请求方式:GET

请求头信息:

参数名 变量 类型【长度限制】 必填 描述
Authorization "Bearer 登录验证产生的accessToken" string 若过期,可通过刷新accessToken来获得新的有效accessToken

返回参数(JSON):

参数名 变量 类型【长度限制】 描述
Time 存储时间(秒) string 该套餐包含的存储时长
Kind 存储类型 int 该套餐的存储方式,目前包含mefs和ipfs
Buysize 购买空间大小 string 该套餐包含的存储空间
Amount 价格 string 该套餐的价格
State 状态 int 该套餐目前是否有效,1表示有效,0表示无效

请求示例:

pkginfos

错误码:

HTTP状态码 错误码 错误描述
401 Authentication Token is Null; Invalid token payload; Invalid token;

 

10. 购买套餐(充值)

请求URL:http://localhost:8081/account/buypkg

请求方式:GET

请求头信息:

参数名 变量 类型【长度限制】 必填 描述
Authorization "Bearer 登录验证产生的accessToken" string 若过期,可通过刷新accessToken来获得新的有效accessToken

请求参数:

参数名 变量 类型【长度限制】 必填 描述
amount 金额 string 花费金额
pkgid 套餐ID string 套餐id,根据‘套餐查询’获得
chainid 链ID string 执行购买套餐的区块链id

返回参数(JSON):

参数名 变量 类型【长度限制】 必填 描述
Status 状态 string 购买成功或失败

请求示例:

buypkg

错误码:

HTTP状态码 错误码 错误描述
401 Authentication Token is Null; Invalid token payload; Invalid token;

 

11. 获取已购买套餐

请求URL:http://localhost:8081/account/getbuypkgs

请求方式:GET

请求头信息:

参数名 变量 类型【长度限制】 必填 描述
Authorization "Bearer 登录验证产生的accessToken" string 若过期,可通过刷新accessToken来获得新的有效accessToken

请求参数:

返回参数(JSON):

参数名 变量 类型【长度限制】 描述
Starttime 开始时间 string 已购买套餐的开始服务时间
Endtime 结束时间 string 已购买套餐的服务结束时间
Kind 类型 int 0: MEFS, 1: IPFS
Buysize 购买大小 int 已购买套餐的存储空间大小
Amount 购买金额 int 已购买套餐的消费金额
State 状态 int 套餐状态

请求示例:

错误码:

HTTP状态码 错误码 错误描述
401 Authentication Token is Null; Invalid token payload; Invalid token;

中间件HTTPAPI说明文档

  1. 登陆验证